CoolFace
Apppublic

VolariGlobal/MachineLearning

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
script.js458 linesDownload Raw Back to root
1// PDF.js Configuration
2pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
3
4// Theme Management
5const themeToggle = document.getElementById('themeToggle');
6const sunIcon = document.getElementById('sunIcon');
7const moonIcon = document.getElementById('moonIcon');
8
9// Initialize theme from localStorage or default to dark
10const currentTheme = localStorage.getItem('theme') || 'dark';
11if (currentTheme === 'light') {
12    document.documentElement.setAttribute('data-theme', 'light');
13    sunIcon.style.display = 'none';
14    moonIcon.style.display = 'block';
15}
16
17// Toggle theme function
18function toggleTheme() {
19    const currentTheme = document.documentElement.getAttribute('data-theme');
20    const newTheme = currentTheme === 'light' ? 'dark' : 'light';
21
22    document.documentElement.setAttribute('data-theme', newTheme);
23    localStorage.setItem('theme', newTheme);
24
25    // Toggle icons
26    if (newTheme === 'light') {
27        sunIcon.style.display = 'none';
28        moonIcon.style.display = 'block';
29    } else {
30        sunIcon.style.display = 'block';
31        moonIcon.style.display = 'none';
32    }
33}
34
35// State Management
36const state = {
37    pdfDoc: null,
38    pageNum: 1,
39    pageRendering: false,
40    pageNumPending: null,
41    scale: 1.5,
42    canvas: document.getElementById('pdfCanvas'),
43    ctx: null
44};
45
46// Initialize
47state.ctx = state.canvas.getContext('2d');
48
49// DOM Elements
50const elements = {
51    loadingOverlay: document.getElementById('loadingOverlay'),
52    pageNum: document.getElementById('pageNum'),
53    pageCount: document.getElementById('pageCount'),
54    currentPageDisplay: document.getElementById('currentPageDisplay'),
55    totalPagesDisplay: document.getElementById('totalPagesDisplay'),
56    zoomLevel: document.getElementById('zoomLevel'),
57    prevPageBtn: document.getElementById('prevPageBtn'),
58    nextPageBtn: document.getElementById('nextPageBtn'),
59    zoomInBtn: document.getElementById('zoomInBtn'),
60    zoomOutBtn: document.getElementById('zoomOutBtn'),
61    fitWidthBtn: document.getElementById('fitWidthBtn'),
62    pageInput: document.getElementById('pageInput'),
63    goToPageBtn: document.getElementById('goToPageBtn'),
64    sidebar: document.getElementById('sidebar'),
65    sidebarToggle: document.getElementById('sidebarToggle'),
66    closeSidebarBtn: document.getElementById('closeSidebarBtn'),
67    searchBtn: document.getElementById('searchBtn'),
68    searchPanel: document.getElementById('searchPanel'),
69    closeSearchBtn: document.getElementById('closeSearchBtn'),
70    fullscreenBtn: document.getElementById('fullscreenBtn'),
71    canvasContainer: document.getElementById('canvasContainer')
72};
73
74// Render Page
75function renderPage(num) {
76    state.pageRendering = true;
77
78    state.pdfDoc.getPage(num).then(page => {
79        const viewport = page.getViewport({ scale: state.scale });
80        state.canvas.height = viewport.height;
81        state.canvas.width = viewport.width;
82
83        const renderContext = {
84            canvasContext: state.ctx,
85            viewport: viewport
86        };
87
88        const renderTask = page.render(renderContext);
89
90        renderTask.promise.then(() => {
91            state.pageRendering = false;
92
93            if (state.pageNumPending !== null) {
94                renderPage(state.pageNumPending);
95                state.pageNumPending = null;
96            }
97
98            // Update UI
99            updatePageDisplay();
100
101            // Hide loading overlay on first render
102            if (num === 1) {
103                elements.loadingOverlay.classList.add('hidden');
104            }
105        });
106    });
107
108    // Update page display immediately
109    elements.pageNum.textContent = num;
110    elements.currentPageDisplay.textContent = num;
111}
112
113// Queue Page Render
114function queueRenderPage(num) {
115    if (state.pageRendering) {
116        state.pageNumPending = num;
117    } else {
118        renderPage(num);
119    }
120}
121
122// Update Page Display
123function updatePageDisplay() {
124    elements.zoomLevel.textContent = Math.round(state.scale * 100) + '%';
125    updateThumbnailSelection();
126}
127
128// Navigate to Previous Page
129function onPrevPage() {
130    if (state.pageNum <= 1) {
131        return;
132    }
133    state.pageNum--;
134    queueRenderPage(state.pageNum);
135}
136
137// Navigate to Next Page
138function onNextPage() {
139    if (state.pageNum >= state.pdfDoc.numPages) {
140        return;
141    }
142    state.pageNum++;
143    queueRenderPage(state.pageNum);
144}
145
146// Zoom In
147function zoomIn() {
148    if (state.scale < 3) {
149        state.scale += 0.25;
150        queueRenderPage(state.pageNum);
151    }
152}
153
154// Zoom Out
155function zoomOut() {
156    if (state.scale > 0.5) {
157        state.scale -= 0.25;
158        queueRenderPage(state.pageNum);
159    }
160}
161
162// Fit to Width
163function fitToWidth() {
164    const containerWidth = elements.canvasContainer.clientWidth - 40;
165    state.pdfDoc.getPage(state.pageNum).then(page => {
166        const viewport = page.getViewport({ scale: 1 });
167        state.scale = containerWidth / viewport.width;
168        queueRenderPage(state.pageNum);
169    });
170}
171
172// Go to Page
173function goToPage() {
174    const pageNumber = parseInt(elements.pageInput.value);
175    if (pageNumber >= 1 && pageNumber <= state.pdfDoc.numPages) {
176        state.pageNum = pageNumber;
177        queueRenderPage(state.pageNum);
178        elements.pageInput.value = '';
179    }
180}
181
182// Toggle Sidebar
183function toggleSidebar() {
184    elements.sidebar.classList.toggle('hidden');
185}
186
187// Toggle Search
188function toggleSearch() {
189    elements.searchPanel.classList.toggle('active');
190    if (elements.searchPanel.classList.contains('active')) {
191        document.getElementById('searchInput').focus();
192    }
193}
194
195// Toggle Fullscreen
196function toggleFullscreen() {
197    if (!document.fullscreenElement) {
198        document.documentElement.requestFullscreen();
199    } else {
200        if (document.exitFullscreen) {
201            document.exitFullscreen();
202        }
203    }
204}
205
206// Event Listeners
207themeToggle.addEventListener('click', toggleTheme);
208elements.prevPageBtn.addEventListener('click', onPrevPage);
209elements.nextPageBtn.addEventListener('click', onNextPage);
210elements.zoomInBtn.addEventListener('click', zoomIn);
211elements.zoomOutBtn.addEventListener('click', zoomOut);
212elements.fitWidthBtn.addEventListener('click', fitToWidth);
213elements.goToPageBtn.addEventListener('click', goToPage);
214elements.sidebarToggle.addEventListener('click', toggleSidebar);
215elements.closeSidebarBtn.addEventListener('click', toggleSidebar);
216elements.searchBtn.addEventListener('click', toggleSearch);
217elements.closeSearchBtn.addEventListener('click', toggleSearch);
218elements.fullscreenBtn.addEventListener('click', toggleFullscreen);
219
220// Enter key for page input
221elements.pageInput.addEventListener('keypress', (e) => {
222    if (e.key === 'Enter') {
223        goToPage();
224    }
225});
226
227// Keyboard Shortcuts
228document.addEventListener('keydown', (e) => {
229    // Arrow keys for navigation
230    if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
231        e.preventDefault();
232        onPrevPage();
233    } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
234        e.preventDefault();
235        onNextPage();
236    }
237
238    // Zoom shortcuts
239    if (e.key === '+' || e.key === '=') {
240        e.preventDefault();
241        zoomIn();
242    } else if (e.key === '-') {
243        e.preventDefault();
244        zoomOut();
245    }
246
247    // Search shortcut
248    if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
249        e.preventDefault();
250        toggleSearch();
251    }
252
253    // Fullscreen shortcut
254    if (e.key === 'f' && !e.ctrlKey && !e.metaKey) {
255        const activeElement = document.activeElement;
256        if (activeElement.tagName !== 'INPUT') {
257            toggleFullscreen();
258        }
259    }
260});
261
262// Search Functionality
263let searchMatches = [];
264let currentMatch = 0;
265
266document.getElementById('searchInput').addEventListener('input', async (e) => {
267    const query = e.target.value.toLowerCase();
268    const results = document.getElementById('searchResults');
269
270    if (query.length < 2) {
271        results.textContent = '';
272        searchMatches = [];
273        return;
274    }
275
276    searchMatches = [];
277
278    // Search through all pages
279    for (let i = 1; i <= state.pdfDoc.numPages; i++) {
280        const page = await state.pdfDoc.getPage(i);
281        const textContent = await page.getTextContent();
282        const text = textContent.items.map(item => item.str).join(' ').toLowerCase();
283
284        if (text.includes(query)) {
285            searchMatches.push(i);
286        }
287    }
288
289    if (searchMatches.length > 0) {
290        results.textContent = `Found ${searchMatches.length} matches`;
291        currentMatch = 0;
292    } else {
293        results.textContent = 'No matches found';
294    }
295});
296
297document.getElementById('searchNextBtn').addEventListener('click', () => {
298    if (searchMatches.length > 0) {
299        currentMatch = (currentMatch + 1) % searchMatches.length;
300        state.pageNum = searchMatches[currentMatch];
301        queueRenderPage(state.pageNum);
302        document.getElementById('searchResults').textContent =
303            `Match ${currentMatch + 1} of ${searchMatches.length} (Page ${searchMatches[currentMatch]})`;
304    }
305});
306
307document.getElementById('searchPrevBtn').addEventListener('click', () => {
308    if (searchMatches.length > 0) {
309        currentMatch = (currentMatch - 1 + searchMatches.length) % searchMatches.length;
310        state.pageNum = searchMatches[currentMatch];
311        queueRenderPage(state.pageNum);
312        document.getElementById('searchResults').textContent =
313            `Match ${currentMatch + 1} of ${searchMatches.length} (Page ${searchMatches[currentMatch]})`;
314    }
315});
316
317// Mouse Wheel Zoom
318elements.canvasContainer.addEventListener('wheel', (e) => {
319    if (e.ctrlKey) {
320        e.preventDefault();
321        if (e.deltaY < 0) {
322            zoomIn();
323        } else {
324            zoomOut();
325        }
326    }
327});
328
329// Load PDF
330const url = 'Machine-Learning-Systems.pdf';
331
332pdfjsLib.getDocument(url).promise.then(pdfDoc => {
333    state.pdfDoc = pdfDoc;
334    elements.pageCount.textContent = pdfDoc.numPages;
335    elements.totalPagesDisplay.textContent = pdfDoc.numPages;
336    elements.pageInput.max = pdfDoc.numPages;
337
338    // Render the first page
339    renderPage(state.pageNum);
340
341    // Generate thumbnails
342    generateThumbnails();
343}).catch(error => {
344    console.error('Error loading PDF:', error);
345    elements.loadingOverlay.innerHTML = `
346        <div style="text-align: center;">
347            <h2 style="color: var(--color-accent-primary); margin-bottom: 1rem;">Error Loading PDF</h2>
348            <p style="color: var(--color-text-secondary);">Please make sure the PDF file is in the same directory as this HTML file.</p>
349            <p style="color: var(--color-text-muted); margin-top: 1rem; font-size: 0.875rem;">Looking for: ${url}</p>
350        </div>
351    `;
352});
353
354// Generate Thumbnails
355let currentThumbnailCount = 20;
356const thumbnailsPerLoad = 20;
357
358async function generateThumbnails(startFrom = 1, count = 20) {
359    const thumbnailsContainer = document.getElementById('thumbnails');
360
361    if (startFrom === 1) {
362        thumbnailsContainer.innerHTML = '<h3 style="font-size: 0.875rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--color-text-secondary);">Pages</h3>';
363    }
364
365    const endAt = Math.min(startFrom + count - 1, state.pdfDoc.numPages);
366
367    for (let i = startFrom; i <= endAt; i++) {
368        const page = await state.pdfDoc.getPage(i);
369        const viewport = page.getViewport({ scale: 0.2 });
370
371        const canvas = document.createElement('canvas');
372        const ctx = canvas.getContext('2d');
373        canvas.height = viewport.height;
374        canvas.width = viewport.width;
375
376        await page.render({
377            canvasContext: ctx,
378            viewport: viewport
379        }).promise;
380
381        const thumbnailDiv = document.createElement('div');
382        thumbnailDiv.className = 'thumbnail-item';
383        thumbnailDiv.dataset.page = i;
384
385        // Create wrapper and label
386        const wrapper = document.createElement('div');
387        wrapper.className = 'thumbnail-canvas-wrapper';
388        wrapper.appendChild(canvas); // Append the actual canvas object with drawing
389
390        const label = document.createElement('div');
391        label.className = 'thumbnail-label';
392        label.textContent = `Page ${i}`;
393
394        thumbnailDiv.appendChild(wrapper);
395        thumbnailDiv.appendChild(label);
396
397        thumbnailDiv.addEventListener('click', () => {
398            state.pageNum = i;
399            queueRenderPage(i);
400            // Highlight selected thumbnail
401            updateThumbnailSelection(); // Modified this line
402        });
403
404        thumbnailsContainer.appendChild(thumbnailDiv);
405    }
406
407    // Remove existing "Show More" button if present
408    const existingShowMore = thumbnailsContainer.querySelector('.show-more-btn');
409    if (existingShowMore) {
410        existingShowMore.remove();
411    }
412
413    // Add "Show More" button if there are more pages
414    if (endAt < state.pdfDoc.numPages) {
415        const showMoreDiv = document.createElement('div');
416        showMoreDiv.className = 'show-more-btn';
417        showMoreDiv.innerHTML = `
418            <button onclick="loadMoreThumbnails()">
419                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
420                    <path d="M12 5V19M5 12L12 19L19 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
421                </svg>
422                Show More (${state.pdfDoc.numPages - endAt} remaining)
423            </button>
424        `;
425        thumbnailsContainer.appendChild(showMoreDiv);
426    }
427
428    // Highlight the current page
429    updateThumbnailSelection();
430}
431
432function loadMoreThumbnails() {
433    const nextStart = currentThumbnailCount + 1;
434    currentThumbnailCount += thumbnailsPerLoad;
435    generateThumbnails(nextStart, thumbnailsPerLoad);
436}
437
438function updateThumbnailSelection() {
439    document.querySelectorAll('.thumbnail-item').forEach(t => {
440        if (parseInt(t.dataset.page) === state.pageNum) {
441            t.classList.add('active');
442            // Scroll to the active thumbnail if it's not in view
443            t.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
444        } else {
445            t.classList.remove('active');
446        }
447    });
448}
449
450// Responsive adjustments
451window.addEventListener('resize', () => {
452    if (window.innerWidth <= 768 && !elements.sidebar.classList.contains('hidden')) {
453        elements.sidebar.classList.add('hidden');
454    }
455});
456
457console.log('PDF Viewer initialized successfully!');
458