Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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.
152 changes: 152 additions & 0 deletions patch2.js
Original file line number Diff line number Diff line change
@@ -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
? \`<span style="background:var(--error-color); color:white; font-size:0.6rem; padding:2px 6px; border-radius:4px; margin-left:8px; vertical-align:middle;">NEW</span>\`
: '';

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
? \`<span style="background:var(--error-color); color:white; font-size:0.6rem; padding:2px 6px; border-radius:4px; margin-left:8px; vertical-align:middle;">NEW</span>\`
: '';

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);
62 changes: 40 additions & 22 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ async function loadPDFDatabase() {

if (shouldUseCache) {
pdfDatabase = cachedData;
prepareSearchIndex(pdfDatabase);
// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderSemesterTabs();
Expand All @@ -477,6 +478,8 @@ async function loadPDFDatabase() {
data: pdfDatabase
}));

prepareSearchIndex(pdfDatabase);

// --- FIX: CALL THIS TO POPULATE UI ---
syncClassSwitcher();
renderPDFs();
Expand All @@ -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
========================================= */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
? `<span style="background:var(--error-color); color:white; font-size:0.6rem; padding:2px 6px; border-radius:4px; margin-left:8px; vertical-align:middle;">NEW</span>`
: '';

Expand All @@ -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

Expand Down
49 changes: 49 additions & 0 deletions test_frontend.py
Original file line number Diff line number Diff line change
@@ -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())