| 1 | import { |
| 2 | enqueueHtmlThumbnail, |
| 3 | enqueueHtmlThumbnails, |
| 4 | getFreshHtmlThumbnailPaths, |
| 5 | type HtmlThumbnailRequest |
| 6 | } from '../io/thumbnails/html-thumbnail-service' |
| 7 | |
| 8 | const HTML_EDITOR_THUMBNAIL_VARIANT = 'cover' |
| 9 | const THUMBNAIL_WIDTH = 640 |
| 10 | const THUMBNAIL_HEIGHT = 360 |
| 11 | |
| 12 | export type HtmlEditorThumbnailEntry = { |
| 13 | id: string |
| 14 | htmlPath?: string | null |
| 15 | designWidth?: number | null |
| 16 | } |
| 17 | |
| 18 | function entryToRequest(entry: HtmlEditorThumbnailEntry): HtmlThumbnailRequest | null { |
| 19 | const resourceId = entry.id.trim() |
| 20 | const sourcePath = typeof entry.htmlPath === 'string' ? entry.htmlPath.trim() : '' |
| 21 | if (!resourceId || !sourcePath) return null |
| 22 | |
| 23 | const designWidth = |
| 24 | typeof entry.designWidth === 'number' && Number.isFinite(entry.designWidth) |
| 25 | ? Math.round(entry.designWidth) |
| 26 | : 1280 |
| 27 | const captureWidth = Math.max(640, Math.min(2048, designWidth)) |
| 28 | |
| 29 | return { |
| 30 | resourceType: 'html-editor', |
| 31 | resourceId, |
| 32 | variant: HTML_EDITOR_THUMBNAIL_VARIANT, |
| 33 | sourcePath, |
| 34 | captureWidth, |
| 35 | captureHeight: Math.round((captureWidth * 9) / 16), |
| 36 | thumbnailWidth: THUMBNAIL_WIDTH, |
| 37 | thumbnailHeight: THUMBNAIL_HEIGHT |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | function entriesToRequests(entries: HtmlEditorThumbnailEntry[]): HtmlThumbnailRequest[] { |
| 42 | const requests: HtmlThumbnailRequest[] = [] |
| 43 | for (const entry of entries) { |
| 44 | const request = entryToRequest(entry) |
| 45 | if (request) requests.push(request) |
| 46 | } |
| 47 | return requests |
| 48 | } |
| 49 | |
| 50 | export async function warmHtmlEditorCoverThumbnails( |
| 51 | entries: HtmlEditorThumbnailEntry[] |
| 52 | ): Promise<Map<string, string>> { |
| 53 | const requests = entriesToRequests(entries) |
| 54 | let freshMap: Map<string, string> |
| 55 | try { |
| 56 | freshMap = await getFreshHtmlThumbnailPaths(requests) |
| 57 | } catch (error) { |
| 58 | console.warn('[html-editor-thumbnail] fresh thumbnail lookup failed', error) |
| 59 | return new Map() |
| 60 | } |
| 61 | |
| 62 | const missing = requests.filter((request) => !freshMap.has(request.resourceId)) |
| 63 | if (missing.length > 0) { |
| 64 | void enqueueHtmlThumbnails(missing).catch((error) => { |
| 65 | console.warn('[html-editor-thumbnail] background warmup failed', error) |
| 66 | }) |
| 67 | } |
| 68 | return freshMap |
| 69 | } |
| 70 | |
| 71 | export function refreshHtmlEditorCoverThumbnail(entry: HtmlEditorThumbnailEntry): void { |
| 72 | const request = entryToRequest(entry) |
| 73 | if (!request) return |
| 74 | void enqueueHtmlThumbnail(request, { force: true }).catch((error) => { |
| 75 | console.warn('[html-editor-thumbnail] refresh failed', error) |
| 76 | }) |
| 77 | } |
| 78 |