2026-02-07 05:51:21 +00:00
|
|
|
package html
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"embed"
|
|
|
|
|
//"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"html/template"
|
|
|
|
|
//"io"
|
|
|
|
|
"io/fs"
|
|
|
|
|
//"math"
|
|
|
|
|
"net/http"
|
|
|
|
|
//"path"
|
|
|
|
|
//"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
//"time"
|
|
|
|
|
|
|
|
|
|
//"github.com/Gleipnir-Technology/nidus-sync/config"
|
|
|
|
|
//"github.com/aarondl/opt/null"
|
|
|
|
|
//"github.com/google/uuid"
|
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
//go:embed template/*
|
|
|
|
|
var embeddedFiles embed.FS
|
|
|
|
|
|
|
|
|
|
type templateSystemEmbed struct {
|
|
|
|
|
nameToTemplate map[string]*template.Template
|
|
|
|
|
sourceFS fs.FS
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 17:38:49 +00:00
|
|
|
func newTemplateSystemEmbed() (templateSystemEmbed, error) {
|
|
|
|
|
ts := templateSystemEmbed{
|
|
|
|
|
sourceFS: embeddedFiles,
|
|
|
|
|
nameToTemplate: make(map[string]*template.Template),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load all templates
|
|
|
|
|
if err := ts.loadAll(); err != nil {
|
|
|
|
|
return ts, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ts, nil
|
|
|
|
|
}
|
2026-02-07 05:51:21 +00:00
|
|
|
func (ts templateSystemEmbed) loadAll() error {
|
2026-02-07 17:38:49 +00:00
|
|
|
// Then, parse all remaining templates into their named slots, adding the shared stuff
|
2026-02-09 21:40:58 +00:00
|
|
|
page_subdirs := []string{"template/rmo", "template/sync"}
|
2026-02-07 17:38:49 +00:00
|
|
|
for _, subdir := range page_subdirs {
|
|
|
|
|
err := ts.loadTemplateSubdir(subdir)
|
2026-02-07 05:51:21 +00:00
|
|
|
if err != nil {
|
2026-02-07 17:38:49 +00:00
|
|
|
return fmt.Errorf("Failed to load subdir '%s': %w", subdir, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts templateSystemEmbed) loadTemplateSubdir(subdir string) error {
|
|
|
|
|
err := fs.WalkDir(ts.sourceFS, subdir, func(path string, d fs.DirEntry, err error) error {
|
|
|
|
|
if err != nil || d.IsDir() {
|
2026-02-07 05:51:21 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 17:38:49 +00:00
|
|
|
content, err := fs.ReadFile(ts.sourceFS, path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error reading template %s: %w", path, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_t := template.New(path)
|
|
|
|
|
addFuncMap(new_t)
|
|
|
|
|
_, err = new_t.Parse(string(content))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error parsing '%s': %w", path, err)
|
2026-02-07 05:51:21 +00:00
|
|
|
}
|
|
|
|
|
short_path := removeLeadingDir(path)
|
2026-02-09 21:40:58 +00:00
|
|
|
shared_subdirs := []string{"template/rmo/component", "template/rmo/layout", "template/sync/component", "template/sync/layout"}
|
2026-02-07 17:38:49 +00:00
|
|
|
for _, shared_subdir := range shared_subdirs {
|
2026-02-07 17:55:25 +00:00
|
|
|
err := ts.addSubdirTemplates(new_t, shared_subdir)
|
2026-02-07 17:38:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to add subdir '%s' templates: %w", shared_subdir, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-09 21:40:58 +00:00
|
|
|
svg_fs, err := fs.Sub(ts.sourceFS, "template/svg")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get template/svg sub")
|
|
|
|
|
}
|
|
|
|
|
err = addSVGTemplates(svg_fs, new_t)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error adding SVG templates: %w", err)
|
|
|
|
|
}
|
2026-02-07 17:55:25 +00:00
|
|
|
ts.nameToTemplate[short_path] = new_t
|
2026-02-07 17:38:49 +00:00
|
|
|
log.Debug().Str("path", short_path).Msg("Loaded page template")
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts templateSystemEmbed) addSubdirTemplates(t *template.Template, subdir string) error {
|
|
|
|
|
var err error
|
2026-02-07 17:55:25 +00:00
|
|
|
//log.Debug().Msg("Adding subdir templates")
|
2026-02-07 17:38:49 +00:00
|
|
|
err = fs.WalkDir(ts.sourceFS, subdir, func(path string, d fs.DirEntry, err error) error {
|
|
|
|
|
if err != nil || d.IsDir() {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
content, err := fs.ReadFile(ts.sourceFS, path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error reading template %s: %w", path, err)
|
|
|
|
|
}
|
2026-02-07 05:51:21 +00:00
|
|
|
|
2026-02-07 17:38:49 +00:00
|
|
|
new_t := template.New(path)
|
|
|
|
|
addFuncMap(new_t)
|
|
|
|
|
_, err = new_t.Parse(string(content))
|
2026-02-07 05:51:21 +00:00
|
|
|
if err != nil {
|
2026-02-07 17:38:49 +00:00
|
|
|
return fmt.Errorf("error parsing '%s': %w", path, err)
|
2026-02-07 05:51:21 +00:00
|
|
|
}
|
2026-02-07 17:38:49 +00:00
|
|
|
short_path := removeLeadingDir(path)
|
|
|
|
|
_, err = t.AddParseTree(short_path, new_t.Tree)
|
2026-02-07 05:51:21 +00:00
|
|
|
if err != nil {
|
2026-02-07 17:38:49 +00:00
|
|
|
return fmt.Errorf("error adding parse tree '%s': %w", path, err)
|
2026-02-07 05:51:21 +00:00
|
|
|
}
|
2026-02-07 17:55:25 +00:00
|
|
|
//ts.nameToTemplate[short_path] = new_t
|
|
|
|
|
log.Debug().Str("path", short_path).Msg("Loaded shared component template")
|
2026-02-07 05:51:21 +00:00
|
|
|
return nil
|
|
|
|
|
})
|
2026-02-07 17:38:49 +00:00
|
|
|
return err
|
2026-02-07 05:51:21 +00:00
|
|
|
}
|
2026-02-07 17:38:49 +00:00
|
|
|
|
2026-02-07 05:51:21 +00:00
|
|
|
func (ts templateSystemEmbed) renderOrError(w http.ResponseWriter, template_name string, context interface{}) {
|
|
|
|
|
buf := &bytes.Buffer{}
|
2026-02-07 17:38:49 +00:00
|
|
|
|
|
|
|
|
// Execute the template directly from the pre-parsed set
|
|
|
|
|
templ, ok := ts.nameToTemplate[template_name]
|
|
|
|
|
if !ok {
|
|
|
|
|
log.Error().Str("template_name", template_name).Msg("Can't find template")
|
2026-02-07 17:55:25 +00:00
|
|
|
RespondError(w, "Failed to find template", nil, http.StatusInternalServerError)
|
|
|
|
|
return
|
2026-02-07 05:51:21 +00:00
|
|
|
}
|
2026-02-07 17:38:49 +00:00
|
|
|
err := templ.Execute(buf, context)
|
2026-02-07 05:51:21 +00:00
|
|
|
if err != nil {
|
2026-02-07 17:38:49 +00:00
|
|
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to render template")
|
2026-02-07 05:51:21 +00:00
|
|
|
RespondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-02-07 17:38:49 +00:00
|
|
|
|
2026-02-07 05:51:21 +00:00
|
|
|
buf.WriteTo(w)
|
|
|
|
|
}
|
|
|
|
|
func loadTemplateEmbedded(sourceFS fs.FS, path string) (*template.Template, error) {
|
|
|
|
|
content, err := fs.ReadFile(sourceFS, "template/"+path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error reading template template/%s: %w", path, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new_t := template.New(path)
|
|
|
|
|
addFuncMap(new_t)
|
|
|
|
|
_, err = new_t.Parse(string(content))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("error parsing '%s': %w", path, err)
|
|
|
|
|
}
|
|
|
|
|
return new_t, nil
|
|
|
|
|
}
|
|
|
|
|
func removeLeadingDir(path string) string {
|
|
|
|
|
parts := strings.SplitN(path, "/", 2)
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
return parts[1]
|
|
|
|
|
}
|
|
|
|
|
return path
|
|
|
|
|
}
|