import { BlockDefinition, ProjectFile, VisualElement } from '../types/blocks';
interface CompileOptions {
blocks: BlockDefinition[];
fileTree: ProjectFile[];
visualElements: VisualElement[];
activeFile?: ProjectFile;
projectName: string;
}
export function compileWeb(options: CompileOptions): string {
const { fileTree, activeFile, blocks } = options;
if (!activeFile) {
// Generate default index.html with all visual elements
return generateDefaultHTML(options);
}
switch (activeFile.type) {
case 'html':
return activeFile.content || generateDefaultHTML(options);
case 'css':
return activeFile.content || '/* Styles */\n';
case 'js':
return activeFile.content || '// JavaScript\n';
default:
return activeFile.content || '';
}
}
function findFileInTree(files: ProjectFile[], id: string): ProjectFile | null {
for (const f of files) {
if (f.id === id) return f;
if (f.children) {
const found = findFileInTree(f.children, id);
if (found) return found;
}
}
return null;
}
function generateDefaultHTML(options: CompileOptions): string {
const { visualElements, projectName, fileTree, activeFile } = options;
// Find linked CSS and JS files
const linkedIds = activeFile?.linkedFiles || [];
const linkedFiles = linkedIds.map(id => findFileInTree(fileTree, id)).filter(Boolean) as ProjectFile[];
const linkedCSS = linkedFiles.filter(f => f.type === 'css');
const linkedJS = linkedFiles.filter(f => f.type === 'js');
// Build link and script tags
const cssLinks = linkedCSS.map(f => ` `).join('\n');
const jsScripts = linkedJS.map(f => ` `).join('\n');
return `
${projectName || 'My Web App'}
${cssLinks}
${visualElements.map(el => renderVisualElement(el, 0)).join('\n')}
${jsScripts}
`;
}
export function renderVisualElement(el: VisualElement, depth: number): string {
const indent = ' '.repeat(depth + 1);
const props = Object.entries(el.props)
.filter(([k, v]) => v !== undefined && v !== null && v !== '' && k !== 'textContent' && k !== 'children')
.map(([k, v]) => {
if (typeof v === 'boolean') return v ? k : '';
if (k === 'style') return '';
return `${k}="${String(v).replace(/"/g, '"')}"`;
})
.filter(Boolean)
.join(' ');
const styles = Object.entries(el.styles)
.map(([k, v]) => `${k.replace(/[A-Z]/g, m => '-' + m.toLowerCase())}: ${v}`)
.join('; ');
const styleAttr = styles ? ` style="${styles}"` : '';
const classAttr = el.classes.length ? ` class="${el.classes.join(' ')}"` : '';
const propsStr = props ? ' ' + props : '';
const attrs = propsStr + styleAttr + classAttr;
const content = el.props.textContent || '';
if (el.children && el.children.length > 0) {
return `${indent}<${el.tagName}${attrs}>
${el.children.map(c => renderVisualElement(c, depth + 1)).join('\n')}
${indent}${el.tagName}>`;
}
if (el.tagName === 'img' || el.tagName === 'input' || el.tagName === 'br') {
return `${indent}<${el.tagName}${attrs} />`;
}
return `${indent}<${el.tagName}${attrs}>${content || ''}${el.tagName}>`;
}
export function compileElectron(options: CompileOptions): string {
const { activeFile } = options;
if (!activeFile) return '// Electron main process\n';
switch (activeFile.type) {
case 'typescript':
return activeFile.content || generateDefaultElectronMain(options);
case 'html':
return activeFile.content || generateDefaultHTML(options);
case 'css':
return activeFile.content || '/* Styles */\n';
default:
return activeFile.content || '';
}
}
function generateDefaultElectronMain(options: CompileOptions): string {
return `import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';
let mainWindow: BrowserWindow | null = null;
function createWindow(): void {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
mainWindow.loadFile('index.html');
mainWindow.on('closed', () => { mainWindow = null; });
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
`;
}
export function compileMaui(options: CompileOptions): string {
const { activeFile } = options;
if (!activeFile) return '\n';
switch (activeFile.type) {
case 'xaml':
return activeFile.content || generateDefaultXaml(options);
case 'csharp':
return activeFile.content || generateDefaultCSharp(options);
default:
return activeFile.content || '';
}
}
function generateDefaultXaml(options: CompileOptions): string {
return `
`;
}
function generateDefaultCSharp(options: CompileOptions): string {
return `namespace ${options.projectName};
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void OnSubmitClicked(object sender, EventArgs e)
{
DisplayAlert("Hello", $"You entered: {inputField.Text}", "OK");
}
}`;
}
export function compileNodeJS(options: CompileOptions): string {
const { activeFile } = options;
if (!activeFile) return '// Node.js server\n';
switch (activeFile.type) {
case 'js':
return activeFile.content || generateDefaultExpress(options);
case 'json':
return activeFile.content || generateDefaultPackageJson(options);
case 'env':
return activeFile.content || '# Environment Variables\nPORT=3000\n';
default:
return activeFile.content || '';
}
}
function generateDefaultExpress(options: CompileOptions): string {
return `const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.get('/', (req, res) => {
res.json({
message: 'Welcome to ${options.projectName}!',
version: '1.0.0',
});
});
// Start server
app.listen(PORT, () => {
console.log(\`Server running on port \${PORT}\`);
});
`;
}
/**
* Generate a complete HTML document for preview purposes.
* Renders visual elements and inlines linked CSS content.
* Does NOT include script tags — the caller injects those.
*/
export function generatePreviewHtml(options: {
visualElements: VisualElement[];
projectName: string;
linkedCssContent: { content: string }[];
}): string {
const { visualElements, projectName, linkedCssContent } = options;
const cssBlocks = linkedCssContent
.filter(f => f.content)
.map(f => f.content)
.join('\n\n');
const styleTag = cssBlocks
? ` `
: '';
return `
${projectName || 'My Web App'}
${styleTag}
${visualElements.map(el => renderVisualElement(el, 0)).join('\n')}
`;
}
function generateDefaultPackageJson(options: CompileOptions): string {
return JSON.stringify({
name: options.projectName.toLowerCase().replace(/\s+/g, '-'),
version: '1.0.0',
description: `${options.projectName} - built with RealBlocks`,
main: 'server.js',
scripts: {
start: 'node server.js',
dev: 'nodemon server.js',
},
dependencies: {
express: '^4.18.2',
cors: '^2.8.5',
},
devDependencies: {
nodemon: '^3.0.0',
},
}, null, 2);
}