/** * Impeccable DevTools Extension - Panel * * Displays findings, provides controls for scanning and overlay toggling, * and allows clicking findings to inspect elements. */ // Match the DevTools theme (light or dark) if (chrome.devtools.panels.themeName === 'dark') { document.documentElement.classList.add('theme-dark'); } const tabId = chrome.devtools.inspectedWindow.tabId; const port = chrome.runtime.connect({ name: `impeccable-panel-${tabId}` }); const badge = document.getElementById('badge'); const container = document.getElementById('findings-container'); const emptyState = document.getElementById('empty-state'); const btnRescan = document.getElementById('btn-rescan'); const btnToggle = document.getElementById('btn-toggle'); const settingsContainer = document.getElementById('settings-container'); const settingsList = document.getElementById('settings-list'); const btnSettings = document.getElementById('btn-settings'); let overlaysVisible = true; let allAntipatterns = []; let disabledRules = []; // Load antipatterns list and disabled rules async function initSettings() { try { const resp = await fetch(chrome.runtime.getURL('detector/antipatterns.json')); allAntipatterns = await resp.json(); } catch { allAntipatterns = []; } const stored = await chrome.storage.sync.get({ disabledRules: [] }); disabledRules = stored.disabledRules; renderSettings(); } function renderSettings() { settingsList.innerHTML = ''; for (const ap of allAntipatterns) { const label = document.createElement('label'); label.className = 'setting-rule'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = !disabledRules.includes(ap.id); checkbox.addEventListener('change', () => toggleRule(ap.id, checkbox.checked)); const text = document.createElement('span'); text.textContent = ap.name; label.appendChild(checkbox); label.appendChild(text); settingsList.appendChild(label); } } async function toggleRule(ruleId, enabled) { if (enabled) { disabledRules = disabledRules.filter(id => id !== ruleId); } else { if (!disabledRules.includes(ruleId)) disabledRules.push(ruleId); } await chrome.storage.sync.set({ disabledRules }); chrome.runtime.sendMessage({ action: 'disabled-rules-changed' }); } // Listen for messages from the service worker port.onMessage.addListener((msg) => { if (msg.action === 'findings' || msg.action === 'state') { renderFindings(msg.findings || []); if (msg.overlaysVisible !== undefined) { overlaysVisible = msg.overlaysVisible; updateToggleButton(); } } if (msg.action === 'overlays-toggled') { overlaysVisible = msg.visible; updateToggleButton(); } if (msg.action === 'navigated') { showScanning(); } }); // Controls btnRescan.addEventListener('click', () => { showScanning(); port.postMessage({ action: 'scan' }); }); btnToggle.addEventListener('click', () => { port.postMessage({ action: 'toggle-overlays' }); }); btnSettings.addEventListener('click', () => { const isVisible = settingsContainer.style.display !== 'none'; settingsContainer.style.display = isVisible ? 'none' : ''; btnSettings.classList.toggle('active', !isVisible); }); function updateToggleButton() { btnToggle.classList.toggle('active', overlaysVisible); btnToggle.title = overlaysVisible ? 'Hide overlays' : 'Show overlays'; } function showScanning() { container.innerHTML = `