From d2bd65ff9b9621dbbe1e90a4adda2e27fd2c3800 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:27:47 +0000 Subject: [PATCH] Perf: Pre-calculate derived data for search and rendering Co-authored-by: MrAlokTech <107493955+MrAlokTech@users.noreply.github.com> --- .jules/bolt.md | 3 + patch2.js | 152 +++++++++++++++++++++++++++++++++++++++++++++++ script.js | 62 ++++++++++++------- test_frontend.py | 49 +++++++++++++++ 4 files changed, 244 insertions(+), 22 deletions(-) create mode 100644 .jules/bolt.md create mode 100644 patch2.js create mode 100644 test_frontend.py diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..313c18c --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-24 - Pre-calculate Derived Data to Optimize List Rendering +**Learning:** Instantiating `Date` objects and repeatedly lowercasing strings per item on every render loop causes significant and observable UI slowdowns in apps dealing with thousands of objects. +**Action:** When filtering or mapping data items, calculate derived variables (e.g., lowercased text (`_searchStr`), boolean checks (`_isNew`), preformatted dates (`_formattedDate`)) once during initialization/cache-load via a centralized `prepareSearchIndex` utility instead of recalcuating per-render. Additionally, skip redundant computations by utilizing early returns (`if (!condition) return false;`) within filter methods instead of combined boolean returns. \ No newline at end of file diff --git a/patch2.js b/patch2.js new file mode 100644 index 0000000..2120dbd --- /dev/null +++ b/patch2.js @@ -0,0 +1,152 @@ +const fs = require('fs'); +let code = fs.readFileSync('script.js', 'utf8'); + +code = code.replace( +` const filteredPdfs = pdfDatabase.filter(pdf => { + const matchesSemester = pdf.semester === currentSemester; + + // NEW: Check if the PDF class matches the UI's current class selection + // Note: If old documents don't have this field, they will be hidden. + const matchesClass = pdf.class === currentClass; + + let matchesCategory = false; + if (currentCategory === 'favorites') { + matchesCategory = favorites.includes(pdf.id); + } else { + matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + } + + const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || + pdf.description.toLowerCase().includes(searchTerm) || + pdf.category.toLowerCase().includes(searchTerm) || + pdf.author.toLowerCase().includes(searchTerm); + + // Update return statement to include matchesClass + return matchesSemester && matchesClass && matchesCategory && matchesSearch; + });`, +` const filteredPdfs = pdfDatabase.filter(pdf => { + if (pdf.semester !== currentSemester) return false; + + // NEW: Check if the PDF class matches the UI's current class selection + // Note: If old documents don't have this field, they will be hidden. + if (pdf.class !== currentClass) return false; + + if (currentCategory === 'favorites') { + if (!favorites.includes(pdf.id)) return false; + } else if (currentCategory !== 'all') { + if (pdf.category !== currentCategory) return false; + } + + if (searchTerm && pdf._searchStr) { + if (!pdf._searchStr.includes(searchTerm)) return false; + } + + return true; + });` +); + +code = code.replace( +` const uploadDateObj = new Date(pdf.uploadDate); + const timeDiff = new Date() - uploadDateObj; + const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days + + const newBadgeHTML = isNew + ? \`NEW\` + : ''; + + const categoryIcons = { + 'Organic': 'fa-flask', + 'Inorganic': 'fa-atom', + 'Physical': 'fa-calculator', + 'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used + }; + const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf'; + + // Formatting Date + const formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + });`, +` const newBadgeHTML = pdf._isNew + ? \`NEW\` + : ''; + + const categoryIcons = { + 'Organic': 'fa-flask', + 'Inorganic': 'fa-atom', + 'Physical': 'fa-calculator', + 'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used + }; + const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf'; + const formattedDate = pdf._formattedDate || '';` +); + +// Add prepareSearchIndex only once! +if (!code.includes('function prepareSearchIndex(data)')) { +code = code.replace( +`/* ========================================= + 6. MAINTENANCE & HOLIDAYS + ========================================= */`, +`function prepareSearchIndex(data) { + const now = new Date(); + data.forEach(pdf => { + const title = pdf.title || ''; + const desc = pdf.description || ''; + const cat = pdf.category || ''; + const author = pdf.author || ''; + pdf._searchStr = \`\${title} \${desc} \${cat} \${author}\`.toLowerCase(); + + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + } else if (pdf.uploadDate) { + uploadDateObj = new Date(pdf.uploadDate); + } else { + uploadDateObj = new Date(0); + } + + const timeDiff = now - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + }); +} + +/* ========================================= + 6. MAINTENANCE & HOLIDAYS + ========================================= */` +); +} + +code = code.replace( +` if (shouldUseCache) { + pdfDatabase = cachedData; + // --- FIX: CALL THIS TO POPULATE UI --- + syncClassSwitcher();`, +` if (shouldUseCache) { + pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- + syncClassSwitcher();` +); + +code = code.replace( +` localStorage.setItem(CACHE_KEY, JSON.stringify({ + timestamp: new Date().getTime(), + data: pdfDatabase + })); + + // --- FIX: CALL THIS TO POPULATE UI --- + syncClassSwitcher();`, +` localStorage.setItem(CACHE_KEY, JSON.stringify({ + timestamp: new Date().getTime(), + data: pdfDatabase + })); + + prepareSearchIndex(pdfDatabase); + + // --- FIX: CALL THIS TO POPULATE UI --- + syncClassSwitcher();` +); + +fs.writeFileSync('script.js', code); diff --git a/script.js b/script.js index 22f399d..8decbdb 100644 --- a/script.js +++ b/script.js @@ -454,6 +454,7 @@ async function loadPDFDatabase() { if (shouldUseCache) { pdfDatabase = cachedData; + prepareSearchIndex(pdfDatabase); // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderSemesterTabs(); @@ -477,6 +478,8 @@ async function loadPDFDatabase() { data: pdfDatabase })); + prepareSearchIndex(pdfDatabase); + // --- FIX: CALL THIS TO POPULATE UI --- syncClassSwitcher(); renderPDFs(); @@ -503,6 +506,32 @@ function hidePreloader() { } } +function prepareSearchIndex(data) { + const now = new Date(); + data.forEach(pdf => { + const title = pdf.title || ''; + const desc = pdf.description || ''; + const cat = pdf.category || ''; + const author = pdf.author || ''; + pdf._searchStr = `${title} ${desc} ${cat} ${author}`.toLowerCase(); + + let uploadDateObj; + if (pdf.uploadDate && typeof pdf.uploadDate.toDate === 'function') { + uploadDateObj = pdf.uploadDate.toDate(); + } else if (pdf.uploadDate) { + uploadDateObj = new Date(pdf.uploadDate); + } else { + uploadDateObj = new Date(0); + } + + const timeDiff = now - uploadDateObj; + pdf._isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); + pdf._formattedDate = uploadDateObj.toLocaleDateString('en-US', { + year: 'numeric', month: 'short', day: 'numeric' + }); + }); +} + /* ========================================= 6. MAINTENANCE & HOLIDAYS ========================================= */ @@ -905,26 +934,23 @@ function renderPDFs() { // Locate renderPDFs() in script.js and update the filter section const filteredPdfs = pdfDatabase.filter(pdf => { - const matchesSemester = pdf.semester === currentSemester; + if (pdf.semester !== currentSemester) return false; // NEW: Check if the PDF class matches the UI's current class selection // Note: If old documents don't have this field, they will be hidden. - const matchesClass = pdf.class === currentClass; + if (pdf.class !== currentClass) return false; - let matchesCategory = false; if (currentCategory === 'favorites') { - matchesCategory = favorites.includes(pdf.id); - } else { - matchesCategory = currentCategory === 'all' || pdf.category === currentCategory; + if (!favorites.includes(pdf.id)) return false; + } else if (currentCategory !== 'all') { + if (pdf.category !== currentCategory) return false; } - const matchesSearch = pdf.title.toLowerCase().includes(searchTerm) || - pdf.description.toLowerCase().includes(searchTerm) || - pdf.category.toLowerCase().includes(searchTerm) || - pdf.author.toLowerCase().includes(searchTerm); + if (searchTerm && pdf._searchStr) { + if (!pdf._searchStr.includes(searchTerm)) return false; + } - // Update return statement to include matchesClass - return matchesSemester && matchesClass && matchesCategory && matchesSearch; + return true; }); updatePDFCount(filteredPdfs.length); @@ -994,11 +1020,7 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { const heartIconClass = isFav ? 'fas' : 'far'; const btnActiveClass = isFav ? 'active' : ''; - const uploadDateObj = new Date(pdf.uploadDate); - const timeDiff = new Date() - uploadDateObj; - const isNew = timeDiff < (7 * 24 * 60 * 60 * 1000); // 7 days - - const newBadgeHTML = isNew + const newBadgeHTML = pdf._isNew ? `NEW` : ''; @@ -1009,11 +1031,7 @@ function createPDFCard(pdf, favoritesList, index = 0, highlightRegex = null) { 'Physics': 'fa-infinity' // Ensure Physics icon is mapped if used }; const categoryIcon = categoryIcons[pdf.category] || 'fa-file-pdf'; - - // Formatting Date - const formattedDate = new Date(pdf.uploadDate).toLocaleDateString('en-US', { - year: 'numeric', month: 'short', day: 'numeric' - }); + const formattedDate = pdf._formattedDate || ''; // Uses global escapeHtml() now diff --git a/test_frontend.py b/test_frontend.py new file mode 100644 index 0000000..33f036b --- /dev/null +++ b/test_frontend.py @@ -0,0 +1,49 @@ +import asyncio +from playwright.async_api import async_playwright +import time +import subprocess + +async def run(): + # Start the HTTP server + server_process = subprocess.Popen(["python3", "-m", "http.server", "8000"]) + + # Wait for the server to start + time.sleep(2) + + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + page = await browser.new_page() + + try: + # Go to the local server + await page.goto("http://localhost:8000") + print("Page loaded successfully.") + + # Wait for any critical content to load, ensuring it runs + await page.wait_for_timeout(3000) + + # Check if pdf grid exists + pdf_grid = await page.query_selector("#pdfGrid") + if pdf_grid: + print("PDF grid found.") + else: + print("Error: PDF grid not found.") + + # Perform a search + search_input = await page.query_selector("#searchInput") + if search_input: + await search_input.fill("physics") + await page.wait_for_timeout(1000) # Wait for debounce and rendering + print("Search input filled and processed.") + else: + print("Error: Search input not found.") + + except Exception as e: + print(f"Test failed: {e}") + + finally: + await browser.close() + # Terminate the server + server_process.terminate() + +asyncio.run(run())