package custom import ( "html/template" "net/http" "reflect" "time" "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/gorilla/csrf" "github.com/tkw1536/goprogram/lib/reflectx" "golang.org/x/exp/slices" ) // baseContextName is the name of the [BaseContext] type var baseContextName = reflectx.TypeOf[BaseContext]().Name() // BaseContext represents a context shared by all the templates. // // This context should always be initialized using the [custom.Update], [custom.New] or [custom.NewForm] functions. // Other invocations might cause an error at runtime. type BaseContext struct { inited bool // has this context been inited? requestWasNil bool // was the passed request nil GeneratedAt time.Time // time this page was generated at // Menu and breadcrumbs Menu []component.MenuItem BaseContextGaps CSRF template.HTML // CSRF Field } // constants that are used in various parts of the template to render stuff const ( errorPrefix template.HTML = `
BaseContext.use() not called" + errorSuffix
requestNilError template.HTML = errorPrefix + "BaseContext.use() called with nil request" + errorSuffix
)
type BaseContextGaps struct {
Crumbs []component.MenuItem
Actions []component.MenuItem
}
func (bcg BaseContextGaps) Clone() BaseContextGaps {
return BaseContextGaps{
Crumbs: slices.Clone(bcg.Crumbs),
Actions: slices.Clone(bcg.Actions),
}
}
// Use updates this context to use the values from the given base.
//
// The given request *must not* be nil.
//
// For convenience the passed context is also returned.
func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps) *BaseContext {
// tc.custom = custom
tc.inited = true
tc.requestWasNil = r == nil
tc.GeneratedAt = time.Now().UTC()
// setup the CSRF field
tc.CSRF = csrfError
if r != nil {
tc.CSRF = csrf.TemplateField(r)
}
// build the menu
tc.Menu = custom.BuildMenu(r)
// build the breadcrumbs
tc.BaseContextGaps = gaps.Clone()
last := len(tc.Crumbs) - 1
for i := range tc.Crumbs {
tc.Crumbs[i].Active = i == last
}
return tc
}
// DoInitCheck is called by the template to check that the BaseContext was initialized properly
func (bc BaseContext) DoInitCheck() template.HTML {
if !bc.inited {
return initError
}
if bc.requestWasNil {
return requestNilError
}
return ""
}
// NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, bcg BaseContextGaps) (ctx BaseFormContext) {
ctx.FormContext = context
ctx.use(custom, r, bcg)
return
}
// Update updates an embedded BaseContext field in context.
//
// Assumes that context is a pointer to a struct type.
// If this is not the case, might call panic().
func (custom *Custom) Update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
ctx := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext)
ctx.use(custom, r, bcg)
return ctx
}
// BaseFormContext combines BaseContext and FormContext
type BaseFormContext struct {
BaseContext
httpx.FormContext
}