templates: Add a proper menu and navigation

This commit is contained in:
Tom Wiesing 2023-01-11 14:24:13 +01:00
parent 0bb7f99fa3
commit a00195be16
No known key found for this signature in database
76 changed files with 336 additions and 233 deletions

View file

@ -10,6 +10,7 @@ import (
"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
@ -25,6 +26,10 @@ type BaseContext struct {
GeneratedAt time.Time // time this page was generated at
// Menu and breadcrumbs
Menu []component.MenuItem
Crumbs []component.MenuItem
CSRF template.HTML // CSRF Field
}
@ -43,7 +48,8 @@ const (
// The given request *must not* be nil.
//
// For convenience the passed context is also returned.
func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
func (tc *BaseContext) use(custom *Custom, r *http.Request, crumbs []component.MenuItem) *BaseContext {
// tc.custom = custom
tc.inited = true
tc.requestWasNil = r == nil
@ -55,9 +61,20 @@ func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
tc.CSRF = csrf.TemplateField(r)
}
// build the menu
tc.Menu = custom.BuildMenu(r)
// build the breadcrumbs
tc.Crumbs = slices.Clone(crumbs)
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
@ -69,26 +86,21 @@ func (bc BaseContext) DoInitCheck() template.HTML {
}
// NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request) (ctx BaseFormContext) {
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, crumbs []component.MenuItem) (ctx BaseFormContext) {
ctx.FormContext = context
ctx.use(custom.Base, r)
ctx.use(custom, r, crumbs)
return
}
// RenderContext is exactly like NewForm, but returns any to be used as httpx.Form.RenderTemplateContext
func (custom *Custom) RenderContext(ctx httpx.FormContext, r *http.Request) any {
return custom.NewForm(ctx, r)
}
// 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) *BaseContext {
func (custom *Custom) Update(context any, r *http.Request, crumbs []component.MenuItem) *BaseContext {
ctx := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext)
ctx.use(custom.Base, r)
ctx.use(custom, r, crumbs)
return ctx
}

View file

@ -2,16 +2,20 @@ package custom
import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
)
// Custom implements theme and page customization.
type Custom struct {
component.Base
Dependencies struct {
// nothing yet
Routeables []component.Routeable
Menuable []component.Menuable
}
menu lazy.Lazy[[]component.MenuItem]
}
var (
_ component.Backupable = (*Custom)(nil)
_ component.Menuable = (*Custom)(nil)
)

View file

@ -0,0 +1,47 @@
package custom
import (
"html/template"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/pkg/mux"
"golang.org/x/exp/slices"
)
// getMenuItems gets a fresh copy of the cached slice of menu items
func (custom *Custom) Menu(r *http.Request) []component.MenuItem {
return custom.menu.Get(func() []component.MenuItem {
items := make([]component.MenuItem, 0, len(custom.Dependencies.Routeables))
for _, route := range custom.Dependencies.Routeables {
routes := route.Routes()
if routes.MenuTitle == "" {
continue
}
items = append(items, component.MenuItem{
Title: routes.MenuTitle,
Priority: routes.MenuPriority,
Path: template.URL(routes.Prefix),
})
}
slices.SortFunc(items, component.MenuItemSort)
return items
})
}
func (custom *Custom) BuildMenu(r *http.Request) []component.MenuItem {
// NOTE(twiesing): Don't name this method "Menu", as it will cause
// a stack overflow.
path := mux.NormalizePath(r.URL.Path)
// get the static menu items, and then return all the regular ones
var items []component.MenuItem
for _, m := range custom.Dependencies.Menuable {
items = append(items, m.Menu(r)...)
}
for i, item := range items {
items[i].Active = string(item.Path) == path
}
slices.SortFunc(items, component.MenuItemSort)
return items
}