Files
masonry/interpreter/html_interpreter.go
Mason Payne d36e1bfd86 add html and server interpreters
these are basic and lack most features.
the server seems to work the best.
the html on the other hand is really rough and doesn't seem to work yet.
but it does build the pages and they have all the shapes and sections we
wanted. More work to come. :)
2025-08-25 00:50:55 -06:00

708 lines
28 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 sections
for _, section := range page.Sections {
sectionHTML, err := hi.generateSectionHTML(&section, 2)
if err != nil {
return "", err
}
html.WriteString(sectionHTML)
}
// Generate direct components
for _, component := range page.Components {
componentHTML, err := hi.generateComponentHTML(&component, 2)
if err != nil {
return "", err
}
html.WriteString(componentHTML)
}
html.WriteString(" </div>\n")
// JavaScript for interactivity
html.WriteString(" <script>\n")
// API Base URL configuration
apiBaseURL := "http://localhost:8080"
if hi.server != nil {
host := "localhost"
port := 8080
for _, setting := range hi.server.Settings {
if setting.Host != nil {
host = *setting.Host
}
if setting.Port != nil {
port = *setting.Port
}
}
apiBaseURL = fmt.Sprintf("http://%s:%d", host, port)
}
html.WriteString(fmt.Sprintf(" const API_BASE_URL = '%s';\n", apiBaseURL))
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
}
// 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.Type, 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
}