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. :)
708 lines
28 KiB
Go
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, "<", "<")
|
|
s = strings.ReplaceAll(s, ">", ">")
|
|
s = strings.ReplaceAll(s, "\"", """)
|
|
s = strings.ReplaceAll(s, "'", "'")
|
|
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(§ion, 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(§ion, 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
|
|
}
|