UNPKG

boardcast

Version:

Animation library for tabletop game rules on hex boards with CLI tools and game extensions

180 lines (149 loc) 5.4 kB
import { chromium } from 'playwright'; import express from 'express'; import path from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); async function recordTutorial(tutorialFile) { // Validate tutorial file exists if (!fs.existsSync(tutorialFile)) { throw new Error(`Tutorial file not found: ${tutorialFile}`); } console.log(`🎬 Recording tutorial: ${tutorialFile}`); const tutorialPath = path.resolve(tutorialFile); const cliRoot = path.resolve(__dirname, '..'); // Find the built boardcast library const boardcastLibPath = path.resolve(process.cwd(), 'boardcast/dist/lib/index.js'); const contribPath = path.resolve(process.cwd(), 'boardcast/dist/contrib'); if (!fs.existsSync(boardcastLibPath)) { throw new Error(`Boardcast library not found at: ${boardcastLibPath}`); } console.log(`📦 Using boardcast library: ${boardcastLibPath}`); console.log(`📦 Using contrib files: ${contribPath}`); // Create Express server with simple routing const app = express(); const port = 3002; // Use different port to avoid conflicts // Serve the tutorial runner HTML app.get('/', (req, res) => { const html = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Boardcast Tutorial</title> <style> body { margin: 0; padding: 0; background-color: #0a0a0a; overflow: hidden; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; } #chart { background-color: #0a0a0a; display: block; max-width: 95vw; max-height: 95vh; } </style> </head> <body> <svg id="chart" width="1800" height="1000"></svg> <script type="importmap"> { "imports": { "d3": "https://cdn.skypack.dev/d3@7", "boardcast": "/boardcast.js", "boardcast-contrib/": "/boardcast-contrib/" } } </script> <script type="module"> // Import libraries directly from simple paths import { BoardcastHexBoard } from '/boardcast.js'; import { runTutorial, config } from '/tutorial.js'; console.log('Initializing tutorial with config:', config); // Initialize the board const boardConfig = { gridRadius: config?.gridRadius || 8, hexRadius: calculateHexRadius(config?.gridRadius || 8), width: 1800, height: 1000 }; const board = new BoardcastHexBoard('#chart', boardConfig); function calculateHexRadius(gridRadius) { const maxRadius = Math.min(1800, 1000) / (gridRadius * 3); return Math.max(20, Math.min(50, maxRadius)); } // Start tutorial after page loads window.addEventListener('load', async () => { console.log('Starting tutorial...'); try { await new Promise(resolve => setTimeout(resolve, 1000)); await runTutorial(board); console.log('✅ Tutorial completed successfully'); window.tutorialComplete = true; } catch (error) { console.error('❌ Tutorial failed:', error); window.tutorialError = error; } }); </script> </body> </html>`; res.send(html); }); // Serve boardcast library at simple path app.get('/boardcast.js', (req, res) => { res.sendFile(boardcastLibPath); }); // Serve tutorial file at simple path app.get('/tutorial.js', (req, res) => { res.sendFile(tutorialPath); }); // Serve contrib files app.use('/boardcast-contrib', express.static(contribPath)); const server = app.listen(port, () => { console.log(`📡 Server running at http://localhost:${port}`); }); // Create videos directory const videosDir = path.join(process.cwd(), 'videos'); if (!fs.existsSync(videosDir)) { fs.mkdirSync(videosDir, { recursive: true }); } // Launch browser for recording const browser = await chromium.launch({ headless: false }); const context = await browser.newContext({ recordVideo: { dir: videosDir, size: { width: 1920, height: 1080 } }, viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); try { console.log('📱 Loading tutorial...'); await page.goto(`http://localhost:${port}`); await page.waitForSelector('#chart', { state: 'visible' }); console.log('✅ Tutorial page loaded'); // Wait for tutorial completion await page.waitForFunction(() => window.tutorialComplete || window.tutorialError, { timeout: 60000 }); const error = await page.evaluate(() => window.tutorialError); if (error) { throw new Error(`Tutorial execution failed: ${error.message}`); } console.log('🎥 Recording completed successfully'); } catch (error) { console.error('❌ Recording failed:', error.message); } finally { await browser.close(); server.close(); } } export { recordTutorial };