(groupRecord, viewMode = null)
| 19614 | badge.style.zIndex = '11'; |
| 19615 | badge.style.pointerEvents = 'none'; |
| 19616 | // Store update function on wrapper for later use |
| 19617 | thumbnailWrapper._updateBadge = updateBadgeText; |
| 19618 | thumbnailWrapper.appendChild(badge); |
| 19619 | |
| 19620 | } |
| 19621 | |
| 19622 | // Only queue if it doesn't have a thumbnail string AND the flag is false |
| 19623 | if (!currentThumbnail && !hasThumbnailFlag) { |
| 19624 | // During Docker bulk Generate Missing / Regenerate, skip grid WebGL queues — |
| 19625 | // scrolling must not compete with the hidden-window job (OOM). |
| 19626 | if (window._serverBulkThumbnailJobActive) { |
| 19627 | // keep placeholder; job will fill thumbs server-side |
| 19628 | } else if (!pendingThumbnails.has(model.filePath)) { |
| 19629 | pendingThumbnails.add(model.filePath); |
| 19630 | |
| 19631 | enqueueRenderTask({ |
| 19632 | filePath: model.filePath, |
| 19633 | container: thumbnailContainer, |
| 19634 | thumbPriority, |
| 19635 | resolve: async (thumbnail) => { |
| 19636 | // Remove from pending set first |
| 19637 | pendingThumbnails.delete(model.filePath); |
| 19638 | |
| 19639 | // Never persist failure placeholders — leave retryable (hasThumbnail=0 via 3d.png). |
| 19640 | if (!thumbnail || thumbnail === '3d.png' || isFailurePlaceholderThumbnail(thumbnail)) { |
| 19641 | if (thumbnail && isFailurePlaceholderThumbnail(thumbnail)) { |
| 19642 | // Show failure in this cell only; do not write to DB. |
| 19643 | const imgEl = thumbnailContainer.querySelector('img'); |
| 19644 | if (imgEl) imgEl.src = thumbnail; |
| 19645 | } |
| 19646 | // Cell may have been recycled mid-render — re-queue any still-visible placeholder. |
| 19647 | scheduleVisibleThumbnailHydrate(); |
| 19648 | return; |
| 19649 | } |
| 19650 | if (await isMostlyEmptyThumbnailDataUrl(thumbnail)) { |
| 19651 | scheduleVisibleThumbnailHydrate(); |
| 19652 | return; |
| 19653 | } |
| 19654 | |
| 19655 | // Check if model already has multiple thumbnails (from 3MF images). |
| 19656 | // Prefer getModel over getAllThumbnails so the grid path never loads every blob list. |
| 19657 | const modelData = await window.electron.getModel(model.filePath); |
| 19658 | const existingRaw = modelData?.thumbnail || ''; |
| 19659 | const existingMulti = typeof existingRaw === 'string' && existingRaw.includes('::') |
| 19660 | ? existingRaw.split('::').filter((t) => t && t !== '3d.png' && t.startsWith('data:image')) |
| 19661 | : []; |
| 19662 | if (existingMulti.length > 1) { |
| 19663 | model.thumbnail = existingRaw; |
| 19664 | model.hasThumbnail = true; |
| 19665 | setCachedPrimaryThumbnail(model.filePath, existingMulti[0]); |
| 19666 | } else { |
| 19667 | model.thumbnail = thumbnail; |
| 19668 | model.hasThumbnail = true; |
| 19669 | invalidatePrimaryThumbnailCache(model.filePath); |
| 19670 | setCachedPrimaryThumbnail(model.filePath, thumbnail); |
| 19671 | await window.electron.saveThumbnail(model.filePath, thumbnail); |
| 19672 | } |
| 19673 |
no test coverage detected