diff --git a/config/config.go b/config/config.go index 03e8b67..58d3fd5 100644 --- a/config/config.go +++ b/config/config.go @@ -62,6 +62,10 @@ type AmConfig struct { Driver string `yaml:"driver"` Dsn string `yaml:"dsn"` } `yaml:"database"` + Defaults struct { + Language string `yaml:"language"` + TimeZone string `yaml:"timezone"` + } `yaml:"defaults"` Email struct { Host string `yaml:"host"` Port int `yaml:"port"` @@ -144,6 +148,8 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) { dest.Site.UserAgreement.Text = overlayString(loaded.Site.UserAgreement.Text, defaults.Site.UserAgreement.Text) dest.Database.Driver = overlayString(loaded.Database.Driver, defaults.Database.Driver) dest.Database.Dsn = overlayString(loaded.Database.Dsn, defaults.Database.Dsn) + dest.Defaults.Language = overlayString(loaded.Defaults.Language, defaults.Defaults.Language) + dest.Defaults.TimeZone = overlayString(loaded.Defaults.TimeZone, defaults.Defaults.TimeZone) dest.Email.Host = overlayString(loaded.Email.Host, defaults.Email.Host) dest.Email.Port = overlayInt(loaded.Email.Port, defaults.Email.Port) dest.Email.Tls = overlayString(loaded.Email.Tls, defaults.Email.Tls) diff --git a/config/default.yaml b/config/default.yaml index b830c00..88ea7b5 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -20,6 +20,9 @@ site: database: driver: "mysql" dsn: "amsdb:x00yes2k@tcp(localhost)/amsterdam?parseTime=true&loc=Local" +defaults: + language: "en-US" + timezone: "America/Denver" email: host: localhost port: 1025 diff --git a/go.mod b/go.mod index 4f53f03..92aa5a0 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/labstack/echo/v4 v4.13.4 github.com/labstack/gommon v0.4.2 github.com/sirupsen/logrus v1.9.3 + github.com/tkuchiki/go-timezone v0.2.3 golang.org/x/text v0.30.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 017d727..7dc454d 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tkuchiki/go-timezone v0.2.3 h1:D3TVdIPrFsu9lxGxqNX2wsZwn1MZtTqTW0mdevMozHc= +github.com/tkuchiki/go-timezone v0.2.3/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/ui/dialog.go b/ui/dialog.go index 547da8b..c1c2013 100644 --- a/ui/dialog.go +++ b/ui/dialog.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "git.erbosoft.com/amy/amsterdam/config" "git.erbosoft.com/amy/amsterdam/database" "gopkg.in/yaml.v3" ) @@ -173,9 +174,13 @@ func (d *Dialog) Render(ctxt AmContext) (string, any, error) { switch fld.Type { case "password": // clear all "password" fields as a security measure d.Fields[i].Value = "" - case "localelist": // default locale to en-US if we don't have one + case "localelist": // default locale if we don't have one if d.Fields[i].Value == "" { - d.Fields[i].Value = "en-US" + d.Fields[i].Value = config.GlobalConfig.Defaults.Language + } + case "tzlist": // default timezone if we don't have any + if d.Fields[i].Value == "" { + d.Fields[i].Value = config.GlobalConfig.Defaults.TimeZone } } } diff --git a/ui/templates.go b/ui/templates.go index 0e71877..1119763 100644 --- a/ui/templates.go +++ b/ui/templates.go @@ -22,7 +22,6 @@ import ( "sync" "time" "unicode" - _ "unsafe" // HACK HACK HACK "git.erbosoft.com/amy/amsterdam/config" "github.com/CloudyKit/jet/v6" @@ -31,6 +30,7 @@ import ( "github.com/biter777/countries" "github.com/labstack/echo/v4" log "github.com/sirupsen/logrus" + "github.com/tkuchiki/go-timezone" "golang.org/x/text/language" "golang.org/x/text/language/display" ) @@ -79,16 +79,42 @@ func internalGetLanguageList() []Language { slices.SortFunc(cachedLanguageList, func(a Language, b Language) int { return strings.Compare(a.Name, b.Name) }) - log.Infof("internalGetLanguageList() loaded %d languages", len(cachedLanguageList)) } return cachedLanguageList } // getLanguageList is a wrapper around the list of languages that can be added to the template context. func getLanguageList(a jet.Arguments) reflect.Value { - rc := internalGetLanguageList() - log.Infof("GetLanguageList() provides %d items", len(rc)) - return reflect.ValueOf(rc) + return reflect.ValueOf(internalGetLanguageList()) +} + +// cachedTimeZoneList is a wrapper around timezone.Timezones() that produces it by timezone name. +var cachedTimeZoneList []string = nil + +// timeZoneListMutex controls access to internalGetTimeZoneList. +var timeZoneListMutex sync.Mutex + +// internalGetTimeZoneList is a wrapper around TimeZone.TimeZones() that sorts and compacts the list. +func internalGetTimeZoneList() []string { + timeZoneListMutex.Lock() + defer timeZoneListMutex.Unlock() + if cachedTimeZoneList == nil { + timezones := timezone.New().Timezones() + ilist := make([]string, 0, len(timezones)*5) + for k, v := range timezones { + ilist = append(ilist, k) + ilist = append(ilist, v...) + } + + slices.Sort(ilist) + cachedTimeZoneList = slices.Compact(ilist) + } + return cachedTimeZoneList +} + +// getTimeZoneList is a wrapper around internalGetTimeZoneList that can be added to the template context. +func getTimeZoneList(a jet.Arguments) reflect.Value { + return reflect.ValueOf(internalGetTimeZoneList()) } // cachedCountryList is the cached country list after sorting. @@ -224,6 +250,7 @@ func SetupTemplates() { views.AddGlobal("GlobalConfig", config.GlobalConfig) views.AddGlobalFunc("GetCountryList", getCountryList) views.AddGlobalFunc("GetLanguageList", getLanguageList) + views.AddGlobalFunc("GetTimeZoneList", getTimeZoneList) views.AddGlobalFunc("GetMonthList", getMonthList) views.AddGlobalFunc("MakeIntRange", makeIntRange) views.AddGlobalFunc("MakeYearRange", makeYearRange) @@ -236,6 +263,7 @@ func SetupTemplates() { // preload the lists in the background go internalGetCountryList() go internalGetLanguageList() + go internalGetTimeZoneList() } // TemplateRenderer is the Renderer instance set into the Echo context at creation time, to render Jet templates. diff --git a/ui/views/dialog.jet b/ui/views/dialog.jet index b7ddf32..bb73682 100644 --- a/ui/views/dialog.jet +++ b/ui/views/dialog.jet @@ -144,9 +144,12 @@ {{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }} {{ if .Required }}*{{ end }} + {{ v := .Value }} {{ else if .Type == "date" }}