camper/pkg/template/render.go

174 lines
5.5 KiB
Go

/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package template
import (
"fmt"
"html/template"
"io"
"math"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/number"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/build"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
)
func adminTemplateFile(name string) string {
return "web/templates/admin/" + name
}
func publicTemplateFile(name string) string {
return "web/templates/public/" + name
}
func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
mustRenderLayout(w, user, company, adminTemplateFile, data, layout, filename)
}
func MustRenderAdminFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
layout := "layout.gohtml"
if httplib.IsHTMxRequest(r) {
layout = "htmx.gohtml"
}
filenames = append([]string{layout}, filenames...)
mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}
func MustRenderAdminNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filename)
}
func MustRenderAdminNoLayoutFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
mustRenderLayout(w, user, company, adminTemplateFile, data, filenames...)
}
func MustRenderPublic(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
layout := "layout.gohtml"
mustRenderLayout(w, user, company, publicTemplateFile, data, layout, filename)
}
func MustRenderPublicNoLayout(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
mustRenderLayout(w, user, company, publicTemplateFile, data, filename)
}
func MustRenderPublicFiles(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, data interface{}, filenames ...string) {
layout := "layout.gohtml"
filenames = append([]string{layout}, filenames...)
mustRenderLayout(w, user, company, publicTemplateFile, data, filenames...)
}
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templateFile func(string) string, data interface{}, templates ...string) {
t := template.New(templates[len(templates)-1])
t.Funcs(template.FuncMap{
"camperVersion": func() string {
return build.Version
},
"gettext": user.Locale.Get,
"pgettext": user.Locale.GetC,
"currentLocale": func() string {
return user.Locale.Language.String()
},
"isLoggedIn": func() bool {
return user.LoggedIn
},
"isAdmin": user.IsAdmin,
"CSRFHeader": func() string {
return fmt.Sprintf(`"%s": "%s"`, auth.CSRFTokenHeader, user.CSRFToken)
},
"CSRFInput": func() template.HTML {
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`, auth.CSRFTokenField, user.CSRFToken))
},
"raw": func(s string) template.HTML {
return template.HTML(s)
},
"replaceAll": func(s, old, new string) string {
return strings.ReplaceAll(s, old, new)
},
"humanizeBytes": func(bytes int64) string {
return humanizeBytes(bytes)
},
"formatPrice": func(price string) string {
return FormatPrice(price, user.Locale.Language, user.Locale.CurrencyPattern, company.DecimalDigits, company.CurrencySymbol)
},
"formatDate": func(time time.Time) template.HTML {
return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006") + "</time>")
},
"formatDateTime": func(time time.Time) template.HTML {
return template.HTML(`<time datetime="` + time.Format(database.ISODateFormat) + `">` + time.Format("02/01/2006 15:04:05") + "</time>")
},
"formatDateAttr": func(time time.Time) string {
return time.Format(database.ISODateFormat)
},
"queryEscape": func(s string) string {
return url.QueryEscape(s)
},
"inc": func(i int) int {
return i + 1
},
"dec": func(i int) int {
return i - 1
},
"int": func(v interface{}) int {
switch v := v.(type) {
case int:
return v
case time.Weekday:
return int(v)
case time.Month:
return int(v)
default:
panic(fmt.Errorf("Could not convert to integer"))
}
},
"hexToDec": func(s string) int {
num, _ := strconv.ParseInt(s, 16, 0)
return int(num)
},
})
templates = append(templates, "form.gohtml")
files := make([]string, len(templates))
for i, tmpl := range templates {
if len(tmpl) > 4 && tmpl[0] == 'w' && tmpl[1] == 'e' && tmpl[2] == 'b' && tmpl[3] == '/' {
files[i] = tmpl
} else {
files[i] = templateFile(tmpl)
}
}
if _, err := t.ParseFiles(files...); err != nil {
panic(err)
}
if rw, ok := w.(http.ResponseWriter); ok {
rw.Header().Set("Content-Type", "text/html; charset=utf-8")
}
if err := t.ExecuteTemplate(w, path.Base(templates[0]), data); err != nil {
panic(err)
}
}
func FormatPrice(price string, language language.Tag, currencyPattern string, decimalDigits int, currencySymbol string) string {
p := message.NewPrinter(language)
f, err := strconv.ParseFloat(price, 64)
if err != nil {
f = math.NaN()
}
return p.Sprintf(currencyPattern, decimalDigits, number.Decimal(f), currencySymbol)
}