// Mini-map navigation for PDF pages // Procedural implementation (no OOP) interface PDFMiniMapState { miniMap: HTMLElement; currentPage: number; totalPages: number; thumbnails: Map; onPageNavigate: (pageNumber: number) => void; } function createPDFMiniMap( container: HTMLElement, onPageNavigate: (pageNumber: number) => void, ): PDFMiniMapState { const miniMap = createMiniMapElement(container); container.appendChild(miniMap); return { miniMap, currentPage: 1, totalPages: 0, thumbnails: new Map(), onPageNavigate, }; } function createMiniMapElement(container: HTMLElement): HTMLElement { const miniMap = document.createElement("div"); miniMap.className = "pdf-minimap"; miniMap.innerHTML = `
Pages
`; const style = document.createElement("style"); style.textContent = getMiniMapStyles(); miniMap.appendChild(style); return miniMap; } async function initializePDFMiniMap( state: PDFMiniMapState, totalPages: number, renderThumbnail: (page: number) => Promise, ): Promise { const newState = { ...state, totalPages }; await generateMiniMapThumbnails(newState, renderThumbnail); setupMiniMapEventListeners(newState); return newState; } async function generateMiniMapThumbnails( state: PDFMiniMapState, renderThumbnail: (page: number) => Promise, ): Promise { const container = state.miniMap.querySelector( ".pdf-minimap-thumbnails", ) as HTMLElement; container.innerHTML = ""; for (let page = 1; page <= state.totalPages; page++) { try { const thumbnail = await renderThumbnail(page); thumbnail.className = "pdf-minimap-thumbnail"; thumbnail.dataset.pageNumber = page.toString(); thumbnail.style.width = "80px"; thumbnail.style.height = "auto"; thumbnail.style.cursor = "pointer"; thumbnail.style.marginBottom = "4px"; container.appendChild(thumbnail); state.thumbnails.set(page, thumbnail); } catch (error) { console.error(`Failed to generate thumbnail for page ${page}:`, error); } } } function setupMiniMapEventListeners(state: PDFMiniMapState): void { const container = state.miniMap.querySelector(".pdf-minimap-thumbnails"); container?.addEventListener("click", (e) => { const thumbnail = (e.target as HTMLElement).closest( ".pdf-minimap-thumbnail", ) as HTMLElement; if (thumbnail) { const pageNumber = parseInt(thumbnail.dataset.pageNumber || "1"); state.onPageNavigate(pageNumber); } }); } function updateMiniMapCurrentPage( state: PDFMiniMapState, pageNumber: number, ): PDFMiniMapState { const indicator = state.miniMap.querySelector( ".pdf-minimap-indicator", ) as HTMLElement; const thumbnail = state.thumbnails.get(pageNumber); if (thumbnail && indicator) { const rect = thumbnail.getBoundingClientRect(); indicator.style.top = `${thumbnail.offsetTop}px`; indicator.style.height = `${rect.height}px`; } state.thumbnails.forEach((thumb, page) => { if (page === pageNumber) { thumb.style.outline = "2px solid var(--accent)"; thumb.style.opacity = "1"; } else { thumb.style.outline = "none"; thumb.style.opacity = "0.7"; } }); return { ...state, currentPage: pageNumber }; } function showMiniMap(state: PDFMiniMapState): void { state.miniMap.style.display = "block"; } function hideMiniMap(state: PDFMiniMapState): void { state.miniMap.style.display = "none"; } function toggleMiniMap(state: PDFMiniMapState): void { const isVisible = state.miniMap.style.display !== "none"; state.miniMap.style.display = isVisible ? "none" : "block"; } function getMiniMapStyles(): string { return ` .pdf-minimap { position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 100px; max-height: 80vh; background: var(--bg-primary); border: 1px solid var(--text-secondary); border-radius: 8px; padding: 8px; overflow-y: auto; z-index: 1000; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } .pdf-minimap-header { font-size: 12px; font-weight: bold; text-align: center; margin-bottom: 8px; color: var(--text-primary); } .pdf-minimap-thumbnails { display: flex; flex-direction: column; align-items: center; gap: 4px; } .pdf-minimap-thumbnail { transition: outline 0.2s, opacity 0.2s; border-radius: 2px; } .pdf-minimap-thumbnail:hover { opacity: 1 !important; outline: 1px solid var(--text-secondary) !important; } .pdf-minimap-indicator { position: absolute; left: 0; right: 0; border-left: 3px solid var(--accent); pointer-events: none; transition: top 0.3s ease-out; } `; }