Files
masonry/interpreter/html_interpreter.go

774 lines
32 KiB
Go

package interpreter
import (
"fmt"
"masonry/lang"
"strings"
)
// HTMLInterpreter converts Masonry AST to HTML/JavaScript
type HTMLInterpreter struct {
entities map[string]*lang.Entity
pages map[string]*lang.Page
server *lang.Server
}
// NewHTMLInterpreter creates a new HTML interpreter
func NewHTMLInterpreter() *HTMLInterpreter {
return &HTMLInterpreter{
entities: make(map[string]*lang.Entity),
pages: make(map[string]*lang.Page),
}
}
// cleanString removes surrounding quotes from string literals
func (hi *HTMLInterpreter) cleanString(s string) string {
if len(s) >= 2 && ((s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'')) {
return s[1 : len(s)-1]
}
return s
}
// escapeHTML escapes HTML special characters
func (hi *HTMLInterpreter) escapeHTML(s string) string {
s = strings.ReplaceAll(s, "&", "&")
s = strings.ReplaceAll(s, "<", "&lt;")
s = strings.ReplaceAll(s, ">", "&gt;")
s = strings.ReplaceAll(s, "\"", "&quot;")
s = strings.ReplaceAll(s, "'", "&#39;")
return s
}
// GenerateHTML converts a Masonry AST to HTML output
func (hi *HTMLInterpreter) GenerateHTML(ast *lang.AST) (map[string]string, error) {
// First pass: collect entities, pages, and server config
for _, def := range ast.Definitions {
if def.Entity != nil {
hi.entities[def.Entity.Name] = def.Entity
}
if def.Page != nil {
hi.pages[def.Page.Name] = def.Page
}
if def.Server != nil {
hi.server = def.Server
}
}
// Second pass: generate HTML for each page
htmlFiles := make(map[string]string)
for pageName, page := range hi.pages {
html, err := hi.generatePageHTML(page)
if err != nil {
return nil, fmt.Errorf("error generating HTML for page %s: %w", pageName, err)
}
htmlFiles[fmt.Sprintf("%s.html", strings.ToLower(pageName))] = html
}
return htmlFiles, nil
}
// generatePageHTML creates HTML for a single page
func (hi *HTMLInterpreter) generatePageHTML(page *lang.Page) (string, error) {
var html strings.Builder
// HTML document structure
html.WriteString("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n")
html.WriteString(" <meta charset=\"UTF-8\">\n")
html.WriteString(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n")
// Page title
title := page.Name
if page.Title != nil {
title = hi.cleanString(*page.Title)
}
html.WriteString(fmt.Sprintf(" <title>%s</title>\n", hi.escapeHTML(title)))
// Meta tags
for _, meta := range page.Meta {
cleanName := hi.cleanString(meta.Name)
cleanContent := hi.cleanString(meta.Content)
html.WriteString(fmt.Sprintf(" <meta name=\"%s\" content=\"%s\">\n",
hi.escapeHTML(cleanName), hi.escapeHTML(cleanContent)))
}
// Basic CSS for styling
html.WriteString(" <style>\n")
html.WriteString(" body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }\n")
html.WriteString(" .container { max-width: 1200px; margin: 0 auto; }\n")
html.WriteString(" .section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }\n")
html.WriteString(" .tabs { border-bottom: 2px solid #ddd; margin-bottom: 20px; }\n")
html.WriteString(" .tab-button { padding: 10px 20px; border: none; background: none; cursor: pointer; }\n")
html.WriteString(" .tab-button.active { background: #007bff; color: white; }\n")
html.WriteString(" .tab-content { display: none; }\n")
html.WriteString(" .tab-content.active { display: block; }\n")
html.WriteString(" .form-group { margin: 15px 0; }\n")
html.WriteString(" .form-group label { display: block; margin-bottom: 5px; font-weight: bold; }\n")
html.WriteString(" .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }\n")
html.WriteString(" .button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; }\n")
html.WriteString(" .button-primary { background: #007bff; color: white; }\n")
html.WriteString(" .button-secondary { background: #6c757d; color: white; }\n")
html.WriteString(" .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); }\n")
html.WriteString(" .modal-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; min-width: 400px; }\n")
html.WriteString(" </style>\n")
html.WriteString("</head>\n<body>\n")
// Page content
html.WriteString(" <div class=\"container\">\n")
html.WriteString(fmt.Sprintf(" <h1>%s</h1>\n", hi.escapeHTML(title)))
// Generate page elements
for _, element := range page.Elements {
if element.Section != nil {
sectionHTML, err := hi.generateSectionHTML(element.Section, 2)
if err != nil {
return "", err
}
html.WriteString(sectionHTML)
}
if element.Component != nil {
componentHTML, err := hi.generateComponentHTML(element.Component, 2)
if err != nil {
return "", err
}
html.WriteString(componentHTML)
}
}
html.WriteString(" </div>\n")
// JavaScript for interactivity
html.WriteString(" <script>\n")
// Generate server configuration code that handles env vars and defaults at runtime
html.WriteString(hi.generateServerConfigJS())
html.WriteString(" \n")
html.WriteString(" // API helper functions\n")
html.WriteString(" async function apiRequest(method, endpoint, data = null) {\n")
html.WriteString(" const config = {\n")
html.WriteString(" method: method,\n")
html.WriteString(" headers: {\n")
html.WriteString(" 'Content-Type': 'application/json',\n")
html.WriteString(" },\n")
html.WriteString(" };\n")
html.WriteString(" \n")
html.WriteString(" if (data) {\n")
html.WriteString(" config.body = JSON.stringify(data);\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" try {\n")
html.WriteString(" const response = await fetch(API_BASE_URL + endpoint, config);\n")
html.WriteString(" \n")
html.WriteString(" if (!response.ok) {\n")
html.WriteString(" throw new Error(`HTTP error! status: ${response.status}`);\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" if (response.status === 204) {\n")
html.WriteString(" return null; // No content\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" return await response.json();\n")
html.WriteString(" } catch (error) {\n")
html.WriteString(" console.error('API request failed:', error);\n")
html.WriteString(" alert('Error: ' + error.message);\n")
html.WriteString(" throw error;\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Entity-specific API functions\n")
// Generate API functions for each entity
for entityName := range hi.entities {
entityLower := strings.ToLower(entityName)
entityPlural := entityLower + "s"
html.WriteString(fmt.Sprintf(" async function list%s() {\n", entityName))
html.WriteString(fmt.Sprintf(" return await apiRequest('GET', '/%s');\n", entityPlural))
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(fmt.Sprintf(" async function get%s(id) {\n", entityName))
html.WriteString(fmt.Sprintf(" return await apiRequest('GET', '/%s/' + id);\n", entityPlural))
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(fmt.Sprintf(" async function create%s(data) {\n", entityName))
html.WriteString(fmt.Sprintf(" return await apiRequest('POST', '/%s', data);\n", entityPlural))
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(fmt.Sprintf(" async function update%s(id, data) {\n", entityName))
html.WriteString(fmt.Sprintf(" return await apiRequest('PUT', '/%s/' + id, data);\n", entityPlural))
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(fmt.Sprintf(" async function delete%s(id) {\n", entityName))
html.WriteString(fmt.Sprintf(" return await apiRequest('DELETE', '/%s/' + id);\n", entityPlural))
html.WriteString(" }\n")
html.WriteString(" \n")
}
html.WriteString(" // Tab functionality\n")
html.WriteString(" function showTab(tabName) {\n")
html.WriteString(" const tabs = document.querySelectorAll('.tab-content');\n")
html.WriteString(" tabs.forEach(tab => tab.classList.remove('active'));\n")
html.WriteString(" const buttons = document.querySelectorAll('.tab-button');\n")
html.WriteString(" buttons.forEach(btn => btn.classList.remove('active'));\n")
html.WriteString(" document.getElementById(tabName).classList.add('active');\n")
html.WriteString(" event.target.classList.add('active');\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Modal functionality\n")
html.WriteString(" function showModal(modalId) {\n")
html.WriteString(" document.getElementById(modalId).style.display = 'block';\n")
html.WriteString(" }\n")
html.WriteString(" function hideModal(modalId) {\n")
html.WriteString(" document.getElementById(modalId).style.display = 'none';\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Enhanced form submission with API integration\n")
html.WriteString(" async function submitForm(formId, entityType = null) {\n")
html.WriteString(" const form = document.getElementById(formId);\n")
html.WriteString(" const formData = new FormData(form);\n")
html.WriteString(" const data = Object.fromEntries(formData);\n")
html.WriteString(" \n")
html.WriteString(" console.log('Form data:', data);\n")
html.WriteString(" \n")
html.WriteString(" if (entityType) {\n")
html.WriteString(" try {\n")
html.WriteString(" const result = await window['create' + entityType](data);\n")
html.WriteString(" console.log('Created:', result);\n")
html.WriteString(" alert(entityType + ' created successfully!');\n")
html.WriteString(" form.reset();\n")
html.WriteString(" // Refresh any tables showing this entity type\n")
html.WriteString(" await refreshTables(entityType);\n")
html.WriteString(" } catch (error) {\n")
html.WriteString(" console.error('Form submission failed:', error);\n")
html.WriteString(" }\n")
html.WriteString(" } else {\n")
html.WriteString(" alert('Form submitted successfully!');\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Load data into tables\n")
html.WriteString(" async function loadTableData(tableId, entityType) {\n")
html.WriteString(" try {\n")
html.WriteString(" const data = await window['list' + entityType]();\n")
html.WriteString(" const table = document.getElementById(tableId);\n")
html.WriteString(" const tbody = table.querySelector('tbody');\n")
html.WriteString(" \n")
html.WriteString(" if (!data || data.length === 0) {\n")
html.WriteString(" tbody.innerHTML = '<tr><td colspan=\"100%\">No data found</td></tr>';\n")
html.WriteString(" return;\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" tbody.innerHTML = '';\n")
html.WriteString(" data.forEach(item => {\n")
html.WriteString(" const row = document.createElement('tr');\n")
html.WriteString(" const headers = table.querySelectorAll('thead th');\n")
html.WriteString(" \n")
html.WriteString(" headers.forEach((header, index) => {\n")
html.WriteString(" const cell = document.createElement('td');\n")
html.WriteString(" const fieldName = header.textContent.toLowerCase().replace(/\\s+/g, '_');\n")
html.WriteString(" cell.textContent = item[fieldName] || item[header.textContent.toLowerCase()] || '';\n")
html.WriteString(" row.appendChild(cell);\n")
html.WriteString(" });\n")
html.WriteString(" \n")
html.WriteString(" // Add action buttons\n")
html.WriteString(" const actionCell = document.createElement('td');\n")
html.WriteString(" actionCell.innerHTML = `\n")
html.WriteString(" <button onclick=\"editItem('${item.id}', '${entityType}')\" class=\"button button-primary\">Edit</button>\n")
html.WriteString(" <button onclick=\"deleteItem('${item.id}', '${entityType}')\" class=\"button button-secondary\">Delete</button>\n")
html.WriteString(" `;\n")
html.WriteString(" row.appendChild(actionCell);\n")
html.WriteString(" \n")
html.WriteString(" tbody.appendChild(row);\n")
html.WriteString(" });\n")
html.WriteString(" } catch (error) {\n")
html.WriteString(" console.error('Failed to load table data:', error);\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Refresh all tables of a specific entity type\n")
html.WriteString(" async function refreshTables(entityType) {\n")
html.WriteString(" const tables = document.querySelectorAll(`table[data-entity=\"${entityType}\"]`);\n")
html.WriteString(" tables.forEach(table => {\n")
html.WriteString(" loadTableData(table.id, entityType);\n")
html.WriteString(" });\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Edit and delete functions\n")
html.WriteString(" async function editItem(id, entityType) {\n")
html.WriteString(" try {\n")
html.WriteString(" const item = await window['get' + entityType](id);\n")
html.WriteString(" console.log('Edit item:', item);\n")
html.WriteString(" // TODO: Populate form with item data for editing\n")
html.WriteString(" alert('Edit functionality coming soon!');\n")
html.WriteString(" } catch (error) {\n")
html.WriteString(" console.error('Failed to fetch item for editing:', error);\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" async function deleteItem(id, entityType) {\n")
html.WriteString(" if (confirm('Are you sure you want to delete this item?')) {\n")
html.WriteString(" try {\n")
html.WriteString(" await window['delete' + entityType](id);\n")
html.WriteString(" alert('Item deleted successfully!');\n")
html.WriteString(" await refreshTables(entityType);\n")
html.WriteString(" } catch (error) {\n")
html.WriteString(" console.error('Failed to delete item:', error);\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" }\n")
html.WriteString(" \n")
html.WriteString(" // Initialize page - load data when page loads\n")
html.WriteString(" document.addEventListener('DOMContentLoaded', function() {\n")
html.WriteString(" // Auto-load data for all tables with data-entity attribute\n")
html.WriteString(" const tables = document.querySelectorAll('table[data-entity]');\n")
html.WriteString(" tables.forEach(table => {\n")
html.WriteString(" const entityType = table.getAttribute('data-entity');\n")
html.WriteString(" loadTableData(table.id, entityType);\n")
html.WriteString(" });\n")
html.WriteString(" });\n")
html.WriteString(" </script>\n")
html.WriteString("</body>\n</html>")
return html.String(), nil
}
// generateServerConfigJS generates JavaScript code that handles server configuration at runtime
func (hi *HTMLInterpreter) generateServerConfigJS() string {
var js strings.Builder
// Default API base URL
js.WriteString(" // Server configuration\n")
js.WriteString(" let apiHost = 'localhost';\n")
js.WriteString(" let apiPort = 8080;\n\n")
if hi.server != nil {
for _, setting := range hi.server.Settings {
if setting.Host != nil {
js.WriteString(hi.generateConfigValueJS("apiHost", setting.Host))
}
if setting.Port != nil {
js.WriteString(hi.generateIntValueJS("apiPort", setting.Port))
}
}
}
js.WriteString(" const API_BASE_URL = `http://${apiHost}:${apiPort}`;\n")
js.WriteString(" console.log('API Base URL:', API_BASE_URL);\n")
return js.String()
}
// generateConfigValueJS generates JavaScript code to resolve a ConfigValue at runtime
func (hi *HTMLInterpreter) generateConfigValueJS(varName string, configValue *lang.ConfigValue) string {
var js strings.Builder
if configValue.Literal != nil {
// Simple literal assignment
js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.Literal))
} else if configValue.EnvVar != nil {
// Environment variable resolution in browser (note: this is limited in browsers)
// For client-side, we'll need to use a different approach since browsers can't access server env vars
// We'll generate code that looks for the env var in localStorage or a global config object
js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", configValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", configValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" %s = window.CONFIG['%s'];\n", varName, configValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", configValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" %s = localStorage.getItem('%s');\n", varName, configValue.EnvVar.Name))
if configValue.EnvVar.Default != nil {
js.WriteString(" } else {\n")
js.WriteString(fmt.Sprintf(" %s = %q;\n", varName, *configValue.EnvVar.Default))
}
js.WriteString(" }\n")
}
return js.String()
}
// generateIntValueJS generates JavaScript code to resolve an IntValue at runtime
func (hi *HTMLInterpreter) generateIntValueJS(varName string, intValue *lang.IntValue) string {
var js strings.Builder
if intValue.Literal != nil {
// Simple literal assignment
js.WriteString(fmt.Sprintf(" %s = %d;\n", varName, *intValue.Literal))
} else if intValue.EnvVar != nil {
// Environment variable resolution for integers
js.WriteString(fmt.Sprintf(" // Check for %s in global config or localStorage\n", intValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" if (window.CONFIG && window.CONFIG['%s']) {\n", intValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" const val = parseInt(window.CONFIG['%s']);\n", intValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName))
js.WriteString(fmt.Sprintf(" } else if (localStorage.getItem('%s')) {\n", intValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" const val = parseInt(localStorage.getItem('%s'));\n", intValue.EnvVar.Name))
js.WriteString(fmt.Sprintf(" if (!isNaN(val)) %s = val;\n", varName))
if intValue.EnvVar.Default != nil {
js.WriteString(" } else {\n")
js.WriteString(fmt.Sprintf(" %s = %s;\n", varName, *intValue.EnvVar.Default))
}
js.WriteString(" }\n")
}
return js.String()
}
// generateSectionHTML creates HTML for a section
func (hi *HTMLInterpreter) generateSectionHTML(section *lang.Section, indent int) (string, error) {
var html strings.Builder
indentStr := strings.Repeat(" ", indent)
sectionType := "container"
if section.Type != nil {
sectionType = *section.Type
}
switch sectionType {
case "tab":
html.WriteString(fmt.Sprintf("%s<div class=\"tabs\" id=\"%s\">\n", indentStr, section.Name))
// Generate tab buttons
for _, element := range section.Elements {
if element.Section != nil && element.Section.Label != nil {
activeClass := ""
if element.Section.Active {
activeClass = " active"
}
cleanLabel := hi.cleanString(*element.Section.Label)
html.WriteString(fmt.Sprintf("%s <button class=\"tab-button%s\" onclick=\"showTab('%s')\">%s</button>\n",
indentStr, activeClass, element.Section.Name, hi.escapeHTML(cleanLabel)))
}
}
// Generate tab content
for _, element := range section.Elements {
if element.Section != nil {
activeClass := ""
if element.Section.Active {
activeClass = " active"
}
html.WriteString(fmt.Sprintf("%s <div class=\"tab-content%s\" id=\"%s\">\n",
indentStr, activeClass, element.Section.Name))
sectionHTML, err := hi.generateSectionHTML(element.Section, indent+2)
if err != nil {
return "", err
}
html.WriteString(sectionHTML)
html.WriteString(fmt.Sprintf("%s </div>\n", indentStr))
}
}
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
case "modal":
html.WriteString(fmt.Sprintf("%s<div class=\"modal\" id=\"%s\">\n", indentStr, section.Name))
html.WriteString(fmt.Sprintf("%s <div class=\"modal-content\">\n", indentStr))
if section.Label != nil {
cleanLabel := hi.cleanString(*section.Label)
html.WriteString(fmt.Sprintf("%s <h3>%s</h3>\n", indentStr, hi.escapeHTML(cleanLabel)))
}
// Generate modal content
for _, element := range section.Elements {
elementHTML, err := hi.generateSectionElementHTML(&element, indent+2)
if err != nil {
return "", err
}
html.WriteString(elementHTML)
}
html.WriteString(fmt.Sprintf("%s <button class=\"button button-secondary\" onclick=\"hideModal('%s')\">Close</button>\n", indentStr, section.Name))
html.WriteString(fmt.Sprintf("%s </div>\n", indentStr))
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
default: // container, panel, master, detail
cssClass := "section"
if section.Class != nil {
cssClass = hi.cleanString(*section.Class)
}
html.WriteString(fmt.Sprintf("%s<div class=\"%s\" id=\"%s\">\n", indentStr, hi.escapeHTML(cssClass), section.Name))
if section.Label != nil {
cleanLabel := hi.cleanString(*section.Label)
html.WriteString(fmt.Sprintf("%s <h3>%s</h3>\n", indentStr, hi.escapeHTML(cleanLabel)))
}
// Generate section content
for _, element := range section.Elements {
elementHTML, err := hi.generateSectionElementHTML(&element, indent+1)
if err != nil {
return "", err
}
html.WriteString(elementHTML)
}
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
}
return html.String(), nil
}
// generateSectionElementHTML creates HTML for section elements
func (hi *HTMLInterpreter) generateSectionElementHTML(element *lang.SectionElement, indent int) (string, error) {
if element.Component != nil {
return hi.generateComponentHTML(element.Component, indent)
}
if element.Section != nil {
return hi.generateSectionHTML(element.Section, indent)
}
if element.When != nil {
return hi.generateWhenConditionHTML(element.When, indent)
}
return "", nil
}
// generateComponentHTML creates HTML for a component
func (hi *HTMLInterpreter) generateComponentHTML(component *lang.Component, indent int) (string, error) {
var html strings.Builder
indentStr := strings.Repeat(" ", indent)
switch component.Type {
case "form":
formId := fmt.Sprintf("form_%s", component.Type)
if component.Entity != nil {
formId = fmt.Sprintf("form_%s", *component.Entity)
}
html.WriteString(fmt.Sprintf("%s<form id=\"%s\" onsubmit=\"event.preventDefault(); submitForm('%s');\">\n", indentStr, formId, formId))
// Generate form fields
for _, element := range component.Elements {
if element.Field != nil {
fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1)
if err != nil {
return "", err
}
html.WriteString(fieldHTML)
}
if element.Button != nil {
buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1)
if err != nil {
return "", err
}
html.WriteString(buttonHTML)
}
}
html.WriteString(fmt.Sprintf("%s <button type=\"submit\" class=\"button button-primary\">Submit</button>\n", indentStr))
html.WriteString(fmt.Sprintf("%s</form>\n", indentStr))
case "table":
html.WriteString(fmt.Sprintf("%s<table class=\"table\">\n", indentStr))
html.WriteString(fmt.Sprintf("%s <thead><tr>\n", indentStr))
// Generate table headers from fields
for _, element := range component.Elements {
if element.Field != nil {
label := element.Field.Name
for _, attr := range element.Field.Attributes {
if attr.Label != nil {
label = hi.cleanString(*attr.Label)
break
}
}
html.WriteString(fmt.Sprintf("%s <th>%s</th>\n", indentStr, hi.escapeHTML(label)))
}
}
html.WriteString(fmt.Sprintf("%s </tr></thead>\n", indentStr))
html.WriteString(fmt.Sprintf("%s <tbody>\n", indentStr))
html.WriteString(fmt.Sprintf("%s <tr><td colspan=\"100%%\">Data will be loaded here...</td></tr>\n", indentStr))
html.WriteString(fmt.Sprintf("%s </tbody>\n", indentStr))
html.WriteString(fmt.Sprintf("%s</table>\n", indentStr))
default:
html.WriteString(fmt.Sprintf("%s<div class=\"component-%s\">\n", indentStr, component.Type))
// Generate component content
for _, element := range component.Elements {
if element.Field != nil {
fieldHTML, err := hi.generateFieldHTML(element.Field, indent+1)
if err != nil {
return "", err
}
html.WriteString(fieldHTML)
}
if element.Button != nil {
buttonHTML, err := hi.generateButtonHTML(element.Button, indent+1)
if err != nil {
return "", err
}
html.WriteString(buttonHTML)
}
}
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
}
return html.String(), nil
}
// generateFieldHTML creates HTML for a field
func (hi *HTMLInterpreter) generateFieldHTML(field *lang.ComponentField, indent int) (string, error) {
var html strings.Builder
indentStr := strings.Repeat(" ", indent)
// Get field attributes
var label, placeholder, defaultValue string
var required bool
var options []string
label = field.Name
for _, attr := range field.Attributes {
if attr.Label != nil {
label = hi.cleanString(*attr.Label)
}
if attr.Placeholder != nil {
placeholder = hi.cleanString(*attr.Placeholder)
}
if attr.Default != nil {
defaultValue = hi.cleanString(*attr.Default)
}
if attr.Required {
required = true
}
if attr.Options != nil {
// Clean each option in the array
options = make([]string, len(attr.Options))
for i, opt := range attr.Options {
options[i] = hi.cleanString(opt)
}
}
}
html.WriteString(fmt.Sprintf("%s<div class=\"form-group\">\n", indentStr))
requiredAttr := ""
if required {
requiredAttr = " required"
}
html.WriteString(fmt.Sprintf("%s <label for=\"%s\">%s</label>\n", indentStr, field.Name, hi.escapeHTML(label)))
switch field.Type {
case "text", "email", "password", "number", "tel", "url":
html.WriteString(fmt.Sprintf("%s <input type=\"%s\" id=\"%s\" name=\"%s\" placeholder=\"%s\" value=\"%s\"%s>\n",
indentStr, field.Type, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr))
case "textarea":
html.WriteString(fmt.Sprintf("%s <textarea id=\"%s\" name=\"%s\" placeholder=\"%s\"%s>%s</textarea>\n",
indentStr, field.Name, field.Name, hi.escapeHTML(placeholder), requiredAttr, hi.escapeHTML(defaultValue)))
case "select":
html.WriteString(fmt.Sprintf("%s <select id=\"%s\" name=\"%s\"%s>\n", indentStr, field.Name, field.Name, requiredAttr))
if !required {
html.WriteString(fmt.Sprintf("%s <option value=\"\">Select an option</option>\n", indentStr))
}
for _, option := range options {
selected := ""
if option == defaultValue {
selected = " selected"
}
html.WriteString(fmt.Sprintf("%s <option value=\"%s\"%s>%s</option>\n",
indentStr, hi.escapeHTML(option), selected, hi.escapeHTML(option)))
}
html.WriteString(fmt.Sprintf("%s </select>\n", indentStr))
case "checkbox":
checked := ""
if defaultValue == "true" {
checked = " checked"
}
html.WriteString(fmt.Sprintf("%s <input type=\"checkbox\" id=\"%s\" name=\"%s\" value=\"true\"%s%s>\n",
indentStr, field.Name, field.Name, checked, requiredAttr))
case "file":
html.WriteString(fmt.Sprintf("%s <input type=\"file\" id=\"%s\" name=\"%s\"%s>\n",
indentStr, field.Name, field.Name, requiredAttr))
default:
html.WriteString(fmt.Sprintf("%s <input type=\"text\" id=\"%s\" name=\"%s\" placeholder=\"%s\" value=\"%s\"%s>\n",
indentStr, field.Name, field.Name, hi.escapeHTML(placeholder), hi.escapeHTML(defaultValue), requiredAttr))
}
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
return html.String(), nil
}
// generateButtonHTML creates HTML for a button
func (hi *HTMLInterpreter) generateButtonHTML(button *lang.ComponentButton, indent int) (string, error) {
var html strings.Builder
indentStr := strings.Repeat(" ", indent)
buttonClass := "button button-primary"
onclick := ""
// Process button attributes
for _, attr := range button.Attributes {
if attr.Style != nil {
// Handle button style
}
if attr.Target != nil {
// Handle button target/onclick
}
}
cleanLabel := hi.cleanString(button.Label)
html.WriteString(fmt.Sprintf("%s<button type=\"button\" class=\"%s\" onclick=\"%s\">%s</button>\n",
indentStr, buttonClass, onclick, hi.escapeHTML(cleanLabel)))
return html.String(), nil
}
// generateWhenConditionHTML creates HTML for conditional content
func (hi *HTMLInterpreter) generateWhenConditionHTML(when *lang.WhenCondition, indent int) (string, error) {
var html strings.Builder
indentStr := strings.Repeat(" ", indent)
// For now, we'll render the content as visible (real implementation would include JavaScript logic)
html.WriteString(fmt.Sprintf("%s<div class=\"conditional-content\" data-when-field=\"%s\" data-when-operator=\"%s\" data-when-value=\"%s\">\n",
indentStr, when.Field, when.Operator, when.Value))
// Generate conditional content
for _, field := range when.Fields {
fieldHTML, err := hi.generateFieldHTML(&field, indent+1)
if err != nil {
return "", err
}
html.WriteString(fieldHTML)
}
for _, section := range when.Sections {
sectionHTML, err := hi.generateSectionHTML(&section, indent+1)
if err != nil {
return "", err
}
html.WriteString(sectionHTML)
}
for _, component := range when.Components {
componentHTML, err := hi.generateComponentHTML(&component, indent+1)
if err != nil {
return "", err
}
html.WriteString(componentHTML)
}
for _, button := range when.Buttons {
buttonHTML, err := hi.generateButtonHTML(&button, indent+1)
if err != nil {
return "", err
}
html.WriteString(buttonHTML)
}
html.WriteString(fmt.Sprintf("%s</div>\n", indentStr))
return html.String(), nil
}