revamped left menus entirely to a new menu definition system

This commit is contained in:
2025-10-13 20:59:33 -06:00
parent ed10c83b01
commit b1270a262e
10 changed files with 183 additions and 114 deletions
-11
View File
@@ -40,7 +40,6 @@ type AmContext interface {
OutputType() string
Parameter(string) string
RemoteIP() string
Render(string) error
ReplaceUser(*database.User)
SaveSession() error
SubRender(string) ([]byte, error)
@@ -163,16 +162,6 @@ func (c *amContext) RemoteIP() string {
return c.echoContext.RealIP()
}
/* Render renders a template to the output. Called at the top level only.
* Parameters:
* name = The name of the tempate to be rendered.
* Returns:
* Standard Go error status.
*/
func (c *amContext) Render(name string) error {
return c.echoContext.Render(c.httprc, name, c)
}
/* ReplaceUser replaces the current user in the context.
* Parameters:
* u - New user to associate with the context.
-52
View File
@@ -1,52 +0,0 @@
/*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
package ui
// AmLeftMenuItem represents an item on a left menu.
type AmLeftMenuItem struct {
Text string
Link string
}
// AmLeftMenu represents a left menu.
type AmLeftMenu struct {
Title string
Items []AmLeftMenuItem
}
// leftMenusRepo holds the possible left menus.
var leftMenusRepo = map[string]AmLeftMenu{}
// SetupLeftMenus parses the left menus into internal data structures.
func SetupLeftMenus() {
// "Front Page" menu (not community specific)
menu := AmLeftMenu{Title: "Front Page", Items: make([]AmLeftMenuItem, 2)}
menu.Items[0] = AmLeftMenuItem{Text: "Calendar", Link: ""}
menu.Items[1] = AmLeftMenuItem{Text: "Chat", Link: ""}
leftMenusRepo["fp"] = menu
// "About" menu (global)
menu = AmLeftMenu{Title: "About This Site", Items: make([]AmLeftMenuItem, 2)}
menu.Items[0] = AmLeftMenuItem{Text: "Documentation", Link: ""}
menu.Items[1] = AmLeftMenuItem{Text: "About Amsterdam", Link: "/about"}
leftMenusRepo["about"] = menu
}
/* augmentWithLeftMenus adds the left menus to the Amsterdam context.
* Parameters:
* ctxt - The context to add the menus to.
*/
func augmentWithLeftMenus(ctxt AmContext) {
mlist := make([]AmLeftMenu, 0, len(leftMenusRepo))
// TODO: check for "in community" status and select menu
mlist = append(mlist, leftMenusRepo["fp"])
mlist = append(mlist, leftMenusRepo["about"])
ctxt.VarMap().Set("amsterdam_leftMenus", mlist)
}
+31
View File
@@ -0,0 +1,31 @@
#
# Amsterdam Web Communities System
# Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
#
menudefs:
- id: "top"
title: "Front Page"
permSet: "user"
items:
- text: "Calendar"
link: "/TODO/calendar"
disabled: true
- text: "Chat"
link: "/TODO/chat"
disabled: true
- id: "fixed"
title: "About This Site"
permSet: "user"
items:
- text: "Documentation"
link: "/TODO/documentation"
disabled: true
- text: "About Amsterdam"
link: "/about"
- text: "System Administration"
link: "/TODO/sysadmin"
permission: "Global.SysAdminAccess"
+80
View File
@@ -0,0 +1,80 @@
/*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
package ui
import (
_ "embed"
"git.erbosoft.com/amy/amsterdam/database"
"gopkg.in/yaml.v3"
)
// MenuItem represents an itrem within a menu definition.
type MenuItem struct {
Text string `yaml:"text"`
Link string `yaml:"link"`
Disabled bool `yaml:"disabled"`
Permission string `yaml:"permission"`
P *MenuDefinition
}
func (mi *MenuItem) Show(ctxt AmContext) bool {
if mi.Permission == "" {
return true
}
u := ctxt.CurrentUser()
var eperm uint16
switch mi.P.PermSet {
case "user":
eperm = u.BaseLevel
default:
eperm = database.AmRole("NotInList").Level()
}
return database.AmTestPermission(mi.Permission, eperm)
}
// MenuDefinition represents a full menu definition.
type MenuDefinition struct {
ID string `yaml:"id"`
Title string `yaml:"title"`
PermSet string `yaml:"permSet"`
Items []MenuItem `yaml:"items"`
}
// MenuDefs represents the set of all menu definitions.
type MenuDefs struct {
D []MenuDefinition `yaml:"menudefs"`
table map[string]*MenuDefinition
}
//go:embed menudefs.yaml
var initMenuData []byte
// menuDefinitions gives the menu definitions.
var menuDefinitions MenuDefs
// init loads the menu definitions.
func init() {
if err := yaml.Unmarshal(initMenuData, &menuDefinitions); err != nil {
panic(err) // can't happen
}
menuDefinitions.table = make(map[string]*MenuDefinition)
for i, d := range menuDefinitions.D {
menuDefinitions.table[d.ID] = &(menuDefinitions.D[i])
for j := range menuDefinitions.D[i].Items {
menuDefinitions.D[i].Items[j].P = &(menuDefinitions.D[i])
}
}
}
// AmMenu returns a menu definition.
func AmMenu(name string) *MenuDefinition {
return menuDefinitions.table[name]
}
+2 -3
View File
@@ -31,11 +31,10 @@ func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any)
case "string":
err = ctxt.String(amctxt.RC(), fmt.Sprintf("%v", data))
case "template":
err = amctxt.Render(fmt.Sprintf("%v", data))
err = ctxt.Render(amctxt.RC(), fmt.Sprintf("%v", data), amctxt)
case "framed_template":
amctxt.VarMap().Set("amsterdam_innerPage", data)
augmentWithLeftMenus(amctxt)
err = amctxt.Render("frame.jet")
err = ctxt.Render(amctxt.RC(), "frame.jet", amctxt)
default:
err = fmt.Errorf("unknown rendering type: %s", command)
}
+15 -35
View File
@@ -21,9 +21,9 @@ import (
"strings"
"sync"
"time"
"unicode"
"git.erbosoft.com/amy/amsterdam/config"
"git.erbosoft.com/amy/amsterdam/util"
"github.com/CloudyKit/jet/v6"
"github.com/CloudyKit/jet/v6/loaders/embedfs"
"github.com/CloudyKit/jet/v6/loaders/multi"
@@ -83,11 +83,6 @@ func internalGetLanguageList() []Language {
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 {
return reflect.ValueOf(internalGetLanguageList())
}
// cachedTimeZoneList is a wrapper around timezone.Timezones() that produces it by timezone name.
var cachedTimeZoneList []string = nil
@@ -112,11 +107,6 @@ func internalGetTimeZoneList() []string {
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.
var cachedCountryList []countries.CountryCode = nil
@@ -148,11 +138,6 @@ func internalGetCountryList() []countries.CountryCode {
return cachedCountryList
}
// getCountryList is a wrapper around countries.All() to be added to the template context.
func getCountryList(a jet.Arguments) reflect.Value {
return reflect.ValueOf(internalGetCountryList())
}
// getMonthList is a simple wrapper that returns the names of the months to the template context.
func getMonthList(a jet.Arguments) reflect.Value {
rc := make([]string, 12)
@@ -221,21 +206,6 @@ func makeYearRange(a jet.Arguments) reflect.Value {
}
}
/* CapitalizeString changes the first character of the string to a capital.
* Parameters:
* s - The string to be capitalized.
* Returns:
* The capitalized string.
*/
func CapitalizeString(s string) string {
runes := []rune(s)
if len(runes) > 0 {
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}
return ""
}
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
func SetupTemplates() {
views = jet.NewSet(
@@ -248,16 +218,26 @@ func SetupTemplates() {
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
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)
views.AddGlobalFunc("GetCountryList", func(a jet.Arguments) reflect.Value {
return reflect.ValueOf(internalGetCountryList())
})
views.AddGlobalFunc("GetLanguageList", func(a jet.Arguments) reflect.Value {
return reflect.ValueOf(internalGetLanguageList())
})
views.AddGlobalFunc("GetTimeZoneList", func(a jet.Arguments) reflect.Value {
return reflect.ValueOf(internalGetTimeZoneList())
})
views.AddGlobalFunc("AmMenu", func(a jet.Arguments) reflect.Value {
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
return reflect.ValueOf(AmMenu(s))
})
views.AddGlobalFunc("CapitalizeString", func(a jet.Arguments) reflect.Value {
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
return reflect.ValueOf(CapitalizeString(s))
return reflect.ValueOf(util.CapitalizeString(s))
})
// preload the lists in the background
+5 -12
View File
@@ -81,18 +81,11 @@
<div class="flex">
<!-- LEFT SIDEBAR -->
<div class="w-48 bg-blue-400 p-2">
{{ range amsterdam_leftMenus }}
<div class="mb-2 mt-2">
<div class="font-bold mb-1">{{ .Title }}</div>
{{ range .Items }}
{{ if .Link != "" }}
<a href="{{ .Link }}" class="text-blue-700 hover:text-blue-900">{{ .Text }}</a>
{{ else }}
<div class="text-gray-500 mb-1">{{ .Text }}</div>
{{ end }}
{{ end }}
</div>
{{ end }}
{{ .SetScratch("__menu", AmMenu("top")) }}
{{ .SubRender("menu_left.jet") | raw }}
<div class="mb-2 mt-2">&nbsp;</div>
{{ .SetScratch("__menu", AmMenu("fixed")) }}
{{ .SubRender("menu_left.jet") | raw }}
</div>
<!-- MAIN CONTENT -->
+22
View File
@@ -0,0 +1,22 @@
{*
* Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*}
{{ menu := .GetScratch("__menu") }}
<div class="mb-2 mt-2">
<div class="font-bold mb-1">{{ menu.Title }}</div>
{{ ctxt := . }}
{{ range menu.Items }}
{{ if .Show(ctxt) }}
{{ if .Disabled }}
<div class="text-gray-500 mb-1">{{ .Text }}</div>
{{ else }}
<a href="{{ .Link }}" class="text-blue-700 hover:text-blue-900">{{ .Text }}</a>
{{ end }}
{{ end }}
{{ end }}
</div>