templating: Move actions into template

This commit is contained in:
Tom Wiesing 2023-01-12 13:55:07 +01:00
parent 313af2b9e3
commit 202599aaeb
No known key found for this signature in database
23 changed files with 153 additions and 105 deletions

View file

@ -84,8 +84,8 @@ type userFormContext struct {
User *models.User User *models.User
} }
func (panel *UserPanel) UserFormContext(last component.MenuItem) func(ctx httpx.FormContext, r *http.Request) any { func (panel *UserPanel) UserFormContext(last component.MenuItem, gaps custom.BaseContextGaps) func(ctx httpx.FormContext, r *http.Request) any {
crumbs := []component.MenuItem{ gaps.Crumbs = []component.MenuItem{
{Title: "User", Path: "/user/"}, {Title: "User", Path: "/user/"},
last, last,
} }
@ -93,7 +93,7 @@ func (panel *UserPanel) UserFormContext(last component.MenuItem) func(ctx httpx.
user, err := panel.Dependencies.Auth.UserOf(r) user, err := panel.Dependencies.Auth.UserOf(r)
uctx := userFormContext{FormContext: ctx} uctx := userFormContext{FormContext: ctx}
panel.Dependencies.Custom.Update(&uctx, r, crumbs) panel.Dependencies.Custom.Update(&uctx, r, gaps)
if err == nil { if err == nil {
uctx.User = &user.User uctx.User = &user.User
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field" "github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
) )
@ -38,7 +39,7 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
FieldTemplate: field.PureCSSFieldTemplate, FieldTemplate: field.PureCSSFieldTemplate,
RenderTemplate: passwordTemplate, RenderTemplate: passwordTemplate,
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Change Password", Path: "/user/password/"}), RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Change Password", Path: "/user/password/"}, custom.BaseContextGaps{}),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) { Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"] old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"]

View file

@ -18,14 +18,6 @@
{{ end }} {{ end }}
</ul> </ul>
</p> </p>
<div class="pure-button-group" role="group" role="Actions">
<a class="pure-button" href="/user/password/">Change Password</a>
{{ if .User.IsTOTPEnabled }}
<a class="pure-button" href="/user/totp/disable/">Disable Passcode (TOTP)</a>
{{ else }}
<a class="pure-button" href="/user/totp/enable/">Enable Passcode (TOTP)</a>
{{ end }}
</div>
<hr /> <hr />
</div> </div>
@ -34,14 +26,11 @@
{{ if (not .User.IsTOTPEnabled) }} {{ if (not .User.IsTOTPEnabled) }}
<div> <div>
<p class="error-message"> <p class="error-message">
TOTP is required to access these. You are an administrator, but do not have TOTP enabled.
Please turn it on to access the admin page.
</p> </p>
</div> </div>
{{ end }} {{ end }}
<div class="pure-button-group" role="group" role="Actions">
<a class="pure-button" href="/admin/">Distillery Admin Page</a>
</div>
<hr />
</div> </div>
{{ end }} {{ end }}

View file

@ -8,6 +8,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field" "github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
@ -33,7 +34,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
}, },
RenderTemplate: totpEnableTemplate, RenderTemplate: totpEnableTemplate,
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"}), RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"}, custom.BaseContextGaps{}),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) { Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
password := values["password"] password := values["password"]
@ -78,9 +79,11 @@ type totpEnrollContext struct {
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler { func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
totpEnrollTemplate := panel.Dependencies.Custom.Template(totpEnrollTemplate) totpEnrollTemplate := panel.Dependencies.Custom.Template(totpEnrollTemplate)
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "User", Path: "/user/"}, Crumbs: []component.MenuItem{
{Title: "Enable TOTP", Path: "/user/totp/enable/"}, {Title: "User", Path: "/user/"},
{Title: "Enable TOTP", Path: "/user/totp/enable/"},
},
} }
return &httpx.Form[struct{}]{ return &httpx.Form[struct{}]{
Fields: []field.Field{ Fields: []field.Field{
@ -103,7 +106,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
FormContext: context, FormContext: context,
}, },
} }
panel.Dependencies.Custom.Update(&ctx.userFormContext, r, crumbs) panel.Dependencies.Custom.Update(&ctx.userFormContext, r, gaps)
if err == nil && user != nil { if err == nil && user != nil {
ctx.userFormContext.User = &user.User ctx.userFormContext.User = &user.User
@ -168,7 +171,7 @@ func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled() return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
}, },
RenderTemplate: totpDisableTemplate, RenderTemplate: totpDisableTemplate,
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Disable TOTP", Path: "/user/totp/disable/"}), RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Disable TOTP", Path: "/user/totp/disable/"}, custom.BaseContextGaps{}),
Validate: func(r *http.Request, values map[string]string) (struct{}, error) { Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
password, otp := values["password"], values["otp"] password, otp := values["password"], values["otp"]

View file

@ -36,13 +36,17 @@ type GrantWithURL struct {
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler { func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
userTemplate := panel.Dependencies.Custom.Template(userTemplate) userTemplate := panel.Dependencies.Custom.Template(userTemplate)
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "User", Path: "/user/"}, Crumbs: []component.MenuItem{
{Title: "User", Path: "/user/"},
},
Actions: []component.MenuItem{
{Title: "Change Password", Path: "/user/password"},
},
} }
return &httpx.HTMLHandler[routeUserContext]{ return &httpx.HTMLHandler[routeUserContext]{
Handler: func(r *http.Request) (ruc routeUserContext, err error) { Handler: func(r *http.Request) (ruc routeUserContext, err error) {
panel.Dependencies.Custom.Update(&ruc, r, crumbs)
// find the user // find the user
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r) ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
@ -50,6 +54,21 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
return ruc, err return ruc, err
} }
// build the gaps
gaps := gaps.Clone()
if ruc.AuthUser.IsTOTPEnabled() {
gaps.Actions = append(gaps.Actions, component.MenuItem{
Title: "Disable Passcode (TOTP)",
Path: "/user/totp/disable/",
})
} else {
gaps.Actions = append(gaps.Actions, component.MenuItem{
Title: "Enable Passcode (TOTP)",
Path: "/user/totp/enable/",
})
}
panel.Dependencies.Custom.Update(&ruc, r, gaps)
// find the grants // find the grants
grants, err := panel.Dependencies.Policy.User(r.Context(), ruc.AuthUser.User.User) grants, err := panel.Dependencies.Policy.User(r.Context(), ruc.AuthUser.User.User)
if err != nil { if err != nil {

View file

@ -9,6 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx" "github.com/FAU-CDI/wisski-distillery/pkg/httpx"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field" "github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -144,8 +145,10 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
if context.Err != nil { if context.Err != nil {
context.Err = errLoginFailed context.Err = errLoginFailed
} }
httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context, r, []component.MenuItem{ httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context, r, custom.BaseContextGaps{
{Title: "Login", Path: template.URL(r.URL.RequestURI())}, Crumbs: []component.MenuItem{
{Title: "Login", Path: template.URL(r.URL.RequestURI())},
},
}), nil, loginTemplate, "", w, r) }), nil, loginTemplate, "", w, r)
}, },

View file

@ -30,9 +30,11 @@ type componentContext struct {
} }
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) { func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
admin.Dependencies.Custom.Update(&cp, r, []component.MenuItem{ admin.Dependencies.Custom.Update(&cp, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Components", Path: "/admin/components/"}, {Title: "Admin", Path: "/admin/"},
{Title: "Components", Path: "/admin/components/"},
},
}) })
cp.Analytics = *admin.Analytics cp.Analytics = *admin.Analytics
@ -56,10 +58,12 @@ type ingredientsContext struct {
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) { func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
admin.Dependencies.Custom.Update(&cp, r, []component.MenuItem{ admin.Dependencies.Custom.Update(&cp, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}, {Title: "Admin", Path: "/admin/"},
{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")}, {Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")},
},
}) })
// find the instance itself! // find the instance itself!

View file

@ -40,10 +40,12 @@ type grantsContext struct {
} }
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) { func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) {
admin.Dependencies.Custom.Update(gc, r, []component.MenuItem{ admin.Dependencies.Custom.Update(gc, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}, {Title: "Admin", Path: "/admin/"},
{Title: "Grants", Path: template.URL("/admin/instance/" + slug + "/grants/")}, {Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
{Title: "Grants", Path: template.URL("/admin/instance/" + slug + "/grants/")},
},
}) })
// find the instance itself // find the instance itself

View file

@ -1,15 +1,6 @@
{{ template "_base.html" . }} {{ template "_base.html" . }}
{{ define "title" }}Admin{{ end }} {{ define "title" }}Admin{{ end }}
{{ define "header" }}
<p>
<div class="pure-button-group" role="group" aria-label="Actions">
<a class="pure-button" href="/admin/users">Users</a>
<a class="pure-button pure-button-small" href="/admin/components">Components</a>
</div>
</p>
{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="pure-u-1-1"> <div class="pure-u-1-1">
<h2 id="overview">Distillery Configuration</h2> <h2 id="overview">Distillery Configuration</h2>

View file

@ -1,15 +1,6 @@
{{ template "_base.html" . }} {{ template "_base.html" . }}
{{ define "title" }}{{ .Instance.Slug }}{{ end }} {{ define "title" }}{{ .Instance.Slug }}{{ end }}
{{ define "header" }}
<p>
<div class="pure-button-group" role="group" aria-label="Actions">
<a class="pure-button" href="/admin/grants/{{ .Info.Slug }}">Grants</a>
<a class="pure-button pure-button-small" href="/admin/ingredients/{{ .Instance.Slug }}">Ingredients</a>
</div>
</p>
{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="pure-u-1-1"> <div class="pure-u-1-1">
<h2 id="overview">Info &amp; Status</h2> <h2 id="overview">Info &amp; Status</h2>

View file

@ -1,13 +1,5 @@
{{ template "_base.html" . }} {{ template "_base.html" . }}
{{ define "title" }}Admin Users{{ end }} {{ define "title" }}Users{{ end }}
{{ define "header" }}
<p>
<div class="pure-button-group" role="group" aria-label="Actions">
<a class="pure-button" href="/admin/users/create">Create New</a>
</div>
</p>
{{ end }}
{{ define "content" }} {{ define "content" }}

View file

@ -87,8 +87,14 @@ type indexContext struct {
} }
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) { func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
admin.Dependencies.Custom.Update(&idx, r, []component.MenuItem{ admin.Dependencies.Custom.Update(&idx, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
},
Actions: []component.MenuItem{
{Title: "Users", Path: "/admin/users/"},
{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
},
}) })
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true) idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
return return

View file

@ -32,9 +32,15 @@ type instanceContext struct {
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) { func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug") slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
admin.Dependencies.Custom.Update(&is, r, []component.MenuItem{ admin.Dependencies.Custom.Update(&is, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}, {Title: "Admin", Path: "/admin/"},
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
},
Actions: []component.MenuItem{
{Title: "Grants", Path: template.URL("/admin/grants/" + slug)},
{Title: "Ingredients", Path: template.URL("/admin/ingredients/" + slug), Priority: component.SmallButton},
},
}) })
// find the instance itself! // find the instance itself!

View file

@ -32,9 +32,11 @@ type userContext struct {
} }
func (admin *Admin) users(r *http.Request) (uc userContext, err error) { func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
admin.Dependencies.Custom.Update(&uc, r, []component.MenuItem{ admin.Dependencies.Custom.Update(&uc, r, custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Users", Path: "/admin/users/"}, {Title: "Admin", Path: "/admin/"},
{Title: "Users", Path: "/admin/users/"},
},
}) })
uc.Error = r.URL.Query().Get("error") uc.Error = r.URL.Query().Get("error")
@ -62,10 +64,15 @@ type createUserResult struct {
func (admin *Admin) createUser(ctx context.Context) http.Handler { func (admin *Admin) createUser(ctx context.Context) http.Handler {
userCreateTemplate := admin.Dependencies.Custom.Template(userCreateTemplate) userCreateTemplate := admin.Dependencies.Custom.Template(userCreateTemplate)
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "Admin", Path: "/admin/"}, Crumbs: []component.MenuItem{
{Title: "Users", Path: "/admin/users"}, {Title: "Admin", Path: "/admin/"},
{Title: "Create", Path: "/admin/users/create"}, {Title: "Users", Path: "/admin/users"},
{Title: "Create", Path: "/admin/users/create"},
},
Actions: []component.MenuItem{
{Title: "Create New", Path: "/admin/users/create/"},
},
} }
return &httpx.Form[createUserResult]{ return &httpx.Form[createUserResult]{
@ -78,7 +85,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
RenderTemplate: userCreateTemplate, RenderTemplate: userCreateTemplate,
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any { RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
return admin.Dependencies.Custom.NewForm(ctx, r, crumbs) return admin.Dependencies.Custom.NewForm(ctx, r, gaps)
}, },
Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) { Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) {

View file

@ -25,8 +25,10 @@ type publicContext struct {
} }
func (home *Home) publicHandler(ctx context.Context) http.Handler { func (home *Home) publicHandler(ctx context.Context) http.Handler {
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "WissKI Distillery", Path: "/"}, Crumbs: []component.MenuItem{
{Title: "WissKI Distillery", Path: "/"},
},
} }
return httpx.HTMLHandler[publicContext]{ return httpx.HTMLHandler[publicContext]{
Handler: func(r *http.Request) (pc publicContext, err error) { Handler: func(r *http.Request) (pc publicContext, err error) {
@ -35,7 +37,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
return pc, httpx.ErrNotFound return pc, httpx.ErrNotFound
} }
home.Dependencies.Custom.Update(&pc, r, crumbs) home.Dependencies.Custom.Update(&pc, r, gaps)
pc.Instances = home.homeInstances.Get(nil) pc.Instances = home.homeInstances.Get(nil)
pc.SelfRedirect = home.Config.SelfRedirect.String() pc.SelfRedirect = home.Config.SelfRedirect.String()

View file

@ -59,8 +59,10 @@ type legalContext struct {
} }
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) { func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
legal.Dependencies.Custom.Update(&lc, r, []component.MenuItem{ legal.Dependencies.Custom.Update(&lc, r, custom.BaseContextGaps{
{Title: "Legal", Path: "/legal/"}, Crumbs: []component.MenuItem{
{Title: "Legal", Path: "/legal/"},
},
}) })
lc.LegalNotices = cli.LegalNotices lc.LegalNotices = cli.LegalNotices

View file

@ -123,8 +123,10 @@ type newsContext struct {
// HandleRoute returns the handler for the requested path // HandleRoute returns the handler for the requested path
func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, error) { func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "News", Path: "/news/"}, Crumbs: []component.MenuItem{
{Title: "News", Path: "/news/"},
},
} }
items, itemsErr := Items() items, itemsErr := Items()
@ -134,7 +136,7 @@ func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, e
return httpx.HTMLHandler[newsContext]{ return httpx.HTMLHandler[newsContext]{
Handler: func(r *http.Request) (nc newsContext, err error) { Handler: func(r *http.Request) (nc newsContext, err error) {
news.Dependencies.Custom.Update(&nc, r, crumbs) news.Dependencies.Custom.Update(&nc, r, gaps)
nc.Items, err = items, itemsErr nc.Items, err = items, itemsErr
return return

View file

@ -27,8 +27,8 @@ type BaseContext struct {
GeneratedAt time.Time // time this page was generated at GeneratedAt time.Time // time this page was generated at
// Menu and breadcrumbs // Menu and breadcrumbs
Menu []component.MenuItem Menu []component.MenuItem
Crumbs []component.MenuItem BaseContextGaps
CSRF template.HTML // CSRF Field CSRF template.HTML // CSRF Field
} }
@ -43,12 +43,24 @@ const (
requestNilError template.HTML = errorPrefix + "<code>BaseContext.use()</code> called with nil request" + errorSuffix requestNilError template.HTML = errorPrefix + "<code>BaseContext.use()</code> 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. // Use updates this context to use the values from the given base.
// //
// The given request *must not* be nil. // The given request *must not* be nil.
// //
// For convenience the passed context is also returned. // For convenience the passed context is also returned.
func (tc *BaseContext) use(custom *Custom, r *http.Request, crumbs []component.MenuItem) *BaseContext { func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps) *BaseContext {
// tc.custom = custom // tc.custom = custom
tc.inited = true tc.inited = true
tc.requestWasNil = r == nil tc.requestWasNil = r == nil
@ -65,7 +77,7 @@ func (tc *BaseContext) use(custom *Custom, r *http.Request, crumbs []component.M
tc.Menu = custom.BuildMenu(r) tc.Menu = custom.BuildMenu(r)
// build the breadcrumbs // build the breadcrumbs
tc.Crumbs = slices.Clone(crumbs) tc.BaseContextGaps = gaps.Clone()
last := len(tc.Crumbs) - 1 last := len(tc.Crumbs) - 1
for i := range tc.Crumbs { for i := range tc.Crumbs {
tc.Crumbs[i].Active = i == last tc.Crumbs[i].Active = i == last
@ -86,9 +98,9 @@ func (bc BaseContext) DoInitCheck() template.HTML {
} }
// NewForm is like New, but returns a new BaseFormContext // NewForm is like New, but returns a new BaseFormContext
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, crumbs []component.MenuItem) (ctx BaseFormContext) { func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, bcg BaseContextGaps) (ctx BaseFormContext) {
ctx.FormContext = context ctx.FormContext = context
ctx.use(custom, r, crumbs) ctx.use(custom, r, bcg)
return return
} }
@ -96,11 +108,11 @@ func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, crumbs
// //
// Assumes that context is a pointer to a struct type. // Assumes that context is a pointer to a struct type.
// If this is not the case, might call panic(). // If this is not the case, might call panic().
func (custom *Custom) Update(context any, r *http.Request, crumbs []component.MenuItem) *BaseContext { func (custom *Custom) Update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
ctx := reflect.ValueOf(context). ctx := reflect.ValueOf(context).
Elem().FieldByName(baseContextName).Addr(). Elem().FieldByName(baseContextName).Addr().
Interface().(*BaseContext) Interface().(*BaseContext)
ctx.use(custom, r, crumbs) ctx.use(custom, r, bcg)
return ctx return ctx
} }

View file

@ -1,5 +1,5 @@
<div class="pure-u-1-1"> <div class="pure-u-1-1">
<h2 id="components">Components</h2> <h2 id="structs">Structs</h2>
</div> </div>
{{ range $name, $comp := .Components }} {{ range $name, $comp := .Components }}

View file

@ -11,7 +11,7 @@
<body> <body>
{{ .BaseContext.DoInitCheck }} {{ .BaseContext.DoInitCheck }}
<nav class="pure-menu pure-menu-horizontal"> <nav class="pure-menu pure-menu-horizontal">
<ul class="pure-menu-list"> <ul class="pure-menu-list" role="menubar">
{{ range .BaseContext.Menu }} {{ range .BaseContext.Menu }}
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}"> <li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}">
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a> <a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
@ -27,7 +27,13 @@
<header> <header>
<h1 id="top">{{ template "title" . }}</h1> <h1 id="top">{{ template "title" . }}</h1>
{{ block "header" . }}<!-- no header by default -->{{ end }} {{ if .BaseContext.Actions }}
<div class="pure-button-group" role="group" aria-label="Actions">
{{ range .BaseContext.Actions }}
<a href="{{ .Path }}" class="pure-button{{ if eq .Priority -1 }} pure-button-small{{end}}">{{ .Title }}</a>
{{ end }}
</div>
{{ end }}
</header> </header>
<main> <main>
<div class="pure-g"> <div class="pure-g">

View file

@ -34,3 +34,7 @@ const (
MenuAdmin MenuAdmin
MenuAuth MenuAuth
) )
const (
SmallButton MenuPriority = -1
)

View file

@ -10,6 +10,7 @@ import (
"github.com/FAU-CDI/wdresolve" "github.com/FAU-CDI/wdresolve"
"github.com/FAU-CDI/wdresolve/resolvers" "github.com/FAU-CDI/wdresolve/resolvers"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
@ -25,6 +26,7 @@ type Resolver struct {
Dependencies struct { Dependencies struct {
Instances *instances.Instances Instances *instances.Instances
Custom *custom.Custom Custom *custom.Custom
Auth *auth.Auth
} }
prefixes lazy.Lazy[map[string]string] // cached prefixes (from the server) prefixes lazy.Lazy[map[string]string] // cached prefixes (from the server)
@ -58,8 +60,10 @@ type resolverContext struct {
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) { func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
resolverTemplate := resolver.Dependencies.Custom.Template(resolverTemplate) resolverTemplate := resolver.Dependencies.Custom.Template(resolverTemplate)
crumbs := []component.MenuItem{ gaps := custom.BaseContextGaps{
{Title: "Resolver", Path: "/wisski/get/"}, Crumbs: []component.MenuItem{
{Title: "Resolver", Path: "/wisski/get/"},
},
} }
logger := zerolog.Ctx(ctx) logger := zerolog.Ctx(ctx)
@ -71,7 +75,11 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
ctx := resolverContext{ ctx := resolverContext{
IndexContext: context, IndexContext: context,
} }
resolver.Dependencies.Custom.Update(&ctx, r, crumbs) resolver.Dependencies.Custom.Update(&ctx, r, gaps)
if !resolver.Dependencies.Auth.Has(auth.User, r) {
ctx.IndexContext.Prefixes = nil
}
httpx.WriteHTML(ctx, nil, resolverTemplate, "", w, r) httpx.WriteHTML(ctx, nil, resolverTemplate, "", w, r)
} }

View file

@ -1,16 +1,14 @@
{{ template "_base.html" . }} {{ template "_base.html" . }}
{{ define "title" }}WissKI Resolver{{ end }} {{ define "title" }}Resolver{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="pure-u-1"> <div class="pure-u-1">
<h1>wdresolve</h1>
<p> <p>
This page implements a resolver for the WissKI Distillery. This page contains the global distillery resolver.
It takes a <b>RDF / Triplestore URI</b> that refers to a <b>WissKI Entity</b> and redirects to the page that displays the entity. It takes a <b>RDF / Triplestore URI</b> that refers to a <b>WissKI Entity</b> and redirects to the page that displays the entity.
</p> </p>
</div> </div>
<div class="pure-u-1"> <div class="pure-u-1">
<h2>Resolve URI</h2>
<form action="." method="GET" class="pure-form" autocomplete="off"> <form action="." method="GET" class="pure-form" autocomplete="off">
<label for="uri">Enter A URI To Resolve:</label> <label for="uri">Enter A URI To Resolve:</label>
<input type="text" id="uri" name="uri" value=""> <input type="text" id="uri" name="uri" value="">