/** * Capture Canvas Preview * * Captures the real simulator canvas as a preview image for examples */ import type { ExampleProject } from '../data/examples'; /** * Wait for all wokwi-elements components to be defined */ async function waitForComponents(): Promise { const componentTags = [ 'wokwi-arduino-uno', 'wokwi-led', 'wokwi-rgb-led', 'wokwi-pushbutton', ]; const promises = componentTags.map((tag) => { if (customElements.get(tag)) { return Promise.resolve(); } return customElements.whenDefined(tag); }); await Promise.all(promises); } /** * Create a temporary canvas with the example circuit */ async function createPreviewCanvas(example: ExampleProject): Promise { // Wait for components to be loaded await waitForComponents(); // Create temporary container const container = document.createElement('div'); container.style.position = 'absolute'; container.style.left = '-10000px'; container.style.width = '800px'; container.style.height = '600px'; container.style.backgroundColor = '#1e1e1e'; document.body.appendChild(container); // Create SVG for rendering const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('width', '800'); svg.setAttribute('height', '600'); svg.style.backgroundColor = '#1e1e1e'; container.appendChild(svg); // Calculate bounds for all components let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; example.components.forEach((comp) => { minX = Math.min(minX, comp.x); maxX = Math.max(maxX, comp.x + 150); minY = Math.min(minY, comp.y); maxY = Math.max(maxY, comp.y + 150); }); // Add padding const padding = 40; minX -= padding; minY -= padding; maxX += padding; maxY += padding; // Calculate scale to fit const contentWidth = maxX - minX; const contentHeight = maxY - minY; const scaleX = 760 / contentWidth; const scaleY = 560 / contentHeight; const scale = Math.min(scaleX, scaleY, 1); // Center offset const offsetX = (800 - contentWidth * scale) / 2 - minX * scale; const offsetY = (600 - contentHeight * scale) / 2 - minY * scale; // Create group for all components const group = document.createElementNS('http://www.w3.org/2000/svg', 'g'); group.setAttribute('transform', `translate(${offsetX}, ${offsetY}) scale(${scale})`); svg.appendChild(group); // Render each component as foreignObject for (const comp of example.components) { const foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'); foreignObject.setAttribute('x', comp.x.toString()); foreignObject.setAttribute('y', comp.y.toString()); foreignObject.setAttribute('width', '150'); foreignObject.setAttribute('height', '150'); // Create the actual wokwi element const element = document.createElement(comp.type); // Set properties Object.entries(comp.properties).forEach(([key, value]) => { if (key !== 'state') { element.setAttribute(key, String(value)); } }); foreignObject.appendChild(element); group.appendChild(foreignObject); } // Draw wires example.wires.forEach((wire) => { const startComp = example.components.find((c) => c.id === wire.start.componentId); const endComp = example.components.find((c) => c.id === wire.end.componentId); if (startComp && endComp) { const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); line.setAttribute('x1', (startComp.x + 75).toString()); line.setAttribute('y1', (startComp.y + 75).toString()); line.setAttribute('x2', (endComp.x + 75).toString()); line.setAttribute('y2', (endComp.y + 75).toString()); line.setAttribute('stroke', wire.color); line.setAttribute('stroke-width', '3'); group.appendChild(line); } }); // Wait for rendering await new Promise((resolve) => setTimeout(resolve, 500)); // Convert SVG to canvas const canvas = document.createElement('canvas'); canvas.width = 800; canvas.height = 600; const ctx = canvas.getContext('2d')!; // Draw background ctx.fillStyle = '#1e1e1e'; ctx.fillRect(0, 0, 800, 600); // Convert SVG to image const svgData = new XMLSerializer().serializeToString(svg); const img = new Image(); const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); const url = URL.createObjectURL(svgBlob); await new Promise((resolve, reject) => { img.onload = resolve; img.onerror = reject; img.src = url; }); ctx.drawImage(img, 0, 0); URL.revokeObjectURL(url); // Cleanup document.body.removeChild(container); return canvas; } /** * Generate a preview image data URL for an example */ export async function generateCanvasPreview(example: ExampleProject): Promise { try { const canvas = await createPreviewCanvas(example); return canvas.toDataURL('image/png'); } catch (error) { console.error('Failed to generate preview for', example.id, error); return ''; } } /** * Generate previews for all examples and return a map */ export async function generateAllPreviews( examples: ExampleProject[] ): Promise> { const previews = new Map(); for (const example of examples) { const preview = await generateCanvasPreview(example); if (preview) { previews.set(example.id, preview); } } return previews; }