templates: Add a proper menu and navigation
This commit is contained in:
parent
0bb7f99fa3
commit
a00195be16
76 changed files with 336 additions and 233 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
47
internal/dis/component/control/static/custom/menu.go
Normal file
47
internal/dis/component/control/static/custom/menu.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue