226 lines
7.3 KiB
Text
226 lines
7.3 KiB
Text
================================================================================
|
|
AGENT 7 - THUMBNAIL GENERATION CODE CHANGES
|
|
File: /home/setup/navidocs/client/src/views/DocumentView.vue
|
|
================================================================================
|
|
|
|
CHANGE 1: Add State Variables (Insert after line ~380, after searchStats computed)
|
|
================================================================================
|
|
|
|
// Thumbnail cache and state for search results
|
|
const thumbnailCache = new Map() // pageNum -> dataURL
|
|
const thumbnailLoading = ref(new Set()) // Track which thumbnails are currently loading
|
|
|
|
================================================================================
|
|
CHANGE 2: Add Thumbnail Functions (Insert after makeTocEntriesClickable(), before renderPage())
|
|
================================================================================
|
|
|
|
/**
|
|
* Generate thumbnail for a specific page
|
|
* @param {number} pageNum - Page number to generate thumbnail for
|
|
* @returns {Promise<string>} Data URL of the thumbnail image
|
|
*/
|
|
async function generateThumbnail(pageNum) {
|
|
// Check cache first
|
|
if (thumbnailCache.has(pageNum)) {
|
|
return thumbnailCache.get(pageNum)
|
|
}
|
|
|
|
// Check if already loading
|
|
if (thumbnailLoading.value.has(pageNum)) {
|
|
// Wait for the thumbnail to be generated
|
|
return new Promise((resolve) => {
|
|
const checkInterval = setInterval(() => {
|
|
if (thumbnailCache.has(pageNum)) {
|
|
clearInterval(checkInterval)
|
|
resolve(thumbnailCache.get(pageNum))
|
|
}
|
|
}, 100)
|
|
})
|
|
}
|
|
|
|
// Mark as loading
|
|
thumbnailLoading.value.add(pageNum)
|
|
|
|
try {
|
|
if (!pdfDoc) {
|
|
throw new Error('PDF document not loaded')
|
|
}
|
|
|
|
const page = await pdfDoc.getPage(pageNum)
|
|
|
|
// Use small scale for thumbnail (0.2 = 20% of original size)
|
|
// This produces roughly 80x100px thumbnails for standard letter-sized pages
|
|
const viewport = page.getViewport({ scale: 0.2 })
|
|
|
|
// Create canvas for thumbnail rendering
|
|
const canvas = document.createElement('canvas')
|
|
const context = canvas.getContext('2d', { alpha: false })
|
|
|
|
if (!context) {
|
|
throw new Error('Failed to get canvas context for thumbnail')
|
|
}
|
|
|
|
canvas.width = viewport.width
|
|
canvas.height = viewport.height
|
|
|
|
// Render page to canvas
|
|
await page.render({
|
|
canvasContext: context,
|
|
viewport: viewport
|
|
}).promise
|
|
|
|
// Convert canvas to data URL
|
|
const dataURL = canvas.toDataURL('image/png', 0.8) // 0.8 quality for smaller file size
|
|
|
|
// Cache the thumbnail
|
|
thumbnailCache.set(pageNum, dataURL)
|
|
|
|
return dataURL
|
|
} catch (err) {
|
|
console.error(`Failed to generate thumbnail for page ${pageNum}:`, err)
|
|
// Return a placeholder or empty data URL
|
|
return ''
|
|
} finally {
|
|
// Remove from loading set
|
|
thumbnailLoading.value.delete(pageNum)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a thumbnail is currently being generated
|
|
* @param {number} pageNum - Page number to check
|
|
* @returns {boolean} True if thumbnail is loading
|
|
*/
|
|
function isThumbnailLoading(pageNum) {
|
|
return thumbnailLoading.value.has(pageNum)
|
|
}
|
|
|
|
/**
|
|
* Get thumbnail for a page, generating if needed
|
|
* @param {number} pageNum - Page number
|
|
* @returns {Promise<string>} Data URL of thumbnail
|
|
*/
|
|
async function getThumbnail(pageNum) {
|
|
return await generateThumbnail(pageNum)
|
|
}
|
|
|
|
/**
|
|
* Clear thumbnail cache (useful when document changes)
|
|
*/
|
|
function clearThumbnailCache() {
|
|
thumbnailCache.clear()
|
|
thumbnailLoading.value.clear()
|
|
}
|
|
|
|
================================================================================
|
|
CHANGE 3: Update resetDocumentState() Function
|
|
================================================================================
|
|
|
|
Find the resetDocumentState() function and add this line at the beginning:
|
|
|
|
async function resetDocumentState() {
|
|
clearImages()
|
|
clearThumbnailCache() // ADD THIS LINE
|
|
|
|
// ... rest of existing code
|
|
}
|
|
|
|
================================================================================
|
|
TEMPLATE EXAMPLE: Search Results with Thumbnails
|
|
================================================================================
|
|
|
|
<!-- Replace or enhance existing Jump List template -->
|
|
<div v-if="jumpListOpen && hitList.length > 0" class="absolute right-6 top-full mt-2 w-96 bg-dark-900/95 backdrop-blur-lg border border-white/10 rounded-lg p-3 shadow-2xl z-50">
|
|
<div class="grid gap-2 max-h-96 overflow-y-auto">
|
|
<button
|
|
v-for="(hit, idx) in hitList.slice(0, 10)"
|
|
:key="idx"
|
|
@click="jumpToHit(idx)"
|
|
class="text-left flex gap-3 px-3 py-2 bg-white/5 hover:bg-white/10 rounded transition-colors border border-white/10"
|
|
:class="{ 'ring-2 ring-pink-400': idx === currentHitIndex }"
|
|
>
|
|
<!-- Thumbnail -->
|
|
<div class="flex-shrink-0">
|
|
<!-- Loading Placeholder -->
|
|
<div
|
|
v-if="isThumbnailLoading(hit.page)"
|
|
class="w-20 h-25 bg-white/10 rounded flex items-center justify-center"
|
|
>
|
|
<div class="w-4 h-4 border-2 border-white/30 border-t-pink-400 rounded-full animate-spin"></div>
|
|
</div>
|
|
|
|
<!-- Thumbnail Image -->
|
|
<img
|
|
v-else
|
|
:src="getThumbnail(hit.page)"
|
|
:alt="`Page ${hit.page} thumbnail`"
|
|
class="w-20 h-auto rounded shadow-md border border-white/20"
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Match Info -->
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center justify-between gap-2 mb-1">
|
|
<span class="text-white/70 text-xs font-mono">Match {{ idx + 1 }}</span>
|
|
<span class="text-white/50 text-xs">Page {{ hit.page }}</span>
|
|
</div>
|
|
<p class="text-white text-sm line-clamp-2">{{ hit.snippet }}</p>
|
|
</div>
|
|
</button>
|
|
|
|
<div v-if="hitList.length > 10" class="text-white/50 text-xs text-center py-2">
|
|
+ {{ hitList.length - 10 }} more matches
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
================================================================================
|
|
CSS ADDITIONS (Add to <style> section if needed)
|
|
================================================================================
|
|
|
|
.thumbnail-container {
|
|
width: 80px;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.thumbnail-loading {
|
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
================================================================================
|
|
USAGE NOTES
|
|
================================================================================
|
|
|
|
1. Thumbnails are cached after first generation
|
|
2. Multiple requests for same page wait for first to complete
|
|
3. Scale of 0.2 produces ~80x100px thumbnails
|
|
4. PNG quality of 0.8 balances size and quality
|
|
5. Call getThumbnail(pageNum) from template (returns Promise<string>)
|
|
6. Check isThumbnailLoading(pageNum) to show loading state
|
|
7. clearThumbnailCache() clears all cached thumbnails
|
|
|
|
================================================================================
|
|
INTEGRATION STATUS
|
|
================================================================================
|
|
|
|
✓ State variables defined
|
|
✓ Core functions implemented
|
|
✓ Caching logic complete
|
|
✓ Loading state management
|
|
✓ Error handling
|
|
✓ Cleanup integration
|
|
□ Template integration (Agent 8)
|
|
□ UI styling (Agent 8)
|
|
□ Testing (Agent 10)
|
|
|
|
================================================================================
|