revamped left menus entirely to a new menu definition system
This commit is contained in:
@@ -76,7 +76,6 @@ func main() {
|
|||||||
ui.SetupTemplates()
|
ui.SetupTemplates()
|
||||||
closer = ui.SetupSessionManager()
|
closer = ui.SetupSessionManager()
|
||||||
defer closer()
|
defer closer()
|
||||||
ui.SetupLeftMenus()
|
|
||||||
|
|
||||||
// Set up Echo.
|
// Set up Echo.
|
||||||
e := setupEcho()
|
e := setupEcho()
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ type AmContext interface {
|
|||||||
OutputType() string
|
OutputType() string
|
||||||
Parameter(string) string
|
Parameter(string) string
|
||||||
RemoteIP() string
|
RemoteIP() string
|
||||||
Render(string) error
|
|
||||||
ReplaceUser(*database.User)
|
ReplaceUser(*database.User)
|
||||||
SaveSession() error
|
SaveSession() error
|
||||||
SubRender(string) ([]byte, error)
|
SubRender(string) ([]byte, error)
|
||||||
@@ -163,16 +162,6 @@ func (c *amContext) RemoteIP() string {
|
|||||||
return c.echoContext.RealIP()
|
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.
|
/* ReplaceUser replaces the current user in the context.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* u - New user to associate with the context.
|
* u - New user to associate with the context.
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
@@ -31,11 +31,10 @@ func sendPageData(ctxt echo.Context, amctxt AmContext, command string, data any)
|
|||||||
case "string":
|
case "string":
|
||||||
err = ctxt.String(amctxt.RC(), fmt.Sprintf("%v", data))
|
err = ctxt.String(amctxt.RC(), fmt.Sprintf("%v", data))
|
||||||
case "template":
|
case "template":
|
||||||
err = amctxt.Render(fmt.Sprintf("%v", data))
|
err = ctxt.Render(amctxt.RC(), fmt.Sprintf("%v", data), amctxt)
|
||||||
case "framed_template":
|
case "framed_template":
|
||||||
amctxt.VarMap().Set("amsterdam_innerPage", data)
|
amctxt.VarMap().Set("amsterdam_innerPage", data)
|
||||||
augmentWithLeftMenus(amctxt)
|
err = ctxt.Render(amctxt.RC(), "frame.jet", amctxt)
|
||||||
err = amctxt.Render("frame.jet")
|
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown rendering type: %s", command)
|
err = fmt.Errorf("unknown rendering type: %s", command)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-35
@@ -21,9 +21,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/multi"
|
"github.com/CloudyKit/jet/v6/loaders/multi"
|
||||||
@@ -83,11 +83,6 @@ func internalGetLanguageList() []Language {
|
|||||||
return cachedLanguageList
|
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.
|
// cachedTimeZoneList is a wrapper around timezone.Timezones() that produces it by timezone name.
|
||||||
var cachedTimeZoneList []string = nil
|
var cachedTimeZoneList []string = nil
|
||||||
|
|
||||||
@@ -112,11 +107,6 @@ func internalGetTimeZoneList() []string {
|
|||||||
return cachedTimeZoneList
|
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.
|
// cachedCountryList is the cached country list after sorting.
|
||||||
var cachedCountryList []countries.CountryCode = nil
|
var cachedCountryList []countries.CountryCode = nil
|
||||||
|
|
||||||
@@ -148,11 +138,6 @@ func internalGetCountryList() []countries.CountryCode {
|
|||||||
return cachedCountryList
|
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.
|
// getMonthList is a simple wrapper that returns the names of the months to the template context.
|
||||||
func getMonthList(a jet.Arguments) reflect.Value {
|
func getMonthList(a jet.Arguments) reflect.Value {
|
||||||
rc := make([]string, 12)
|
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.
|
// SetupTemplates is called to set up the template renderer after the configuration is loaded.
|
||||||
func SetupTemplates() {
|
func SetupTemplates() {
|
||||||
views = jet.NewSet(
|
views = jet.NewSet(
|
||||||
@@ -248,16 +218,26 @@ func SetupTemplates() {
|
|||||||
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
views.AddGlobal("AmsterdamVersion", config.AMSTERDAM_VERSION)
|
||||||
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
views.AddGlobal("AmsterdamCopyright", config.AMSTERDAM_COPYRIGHT)
|
||||||
views.AddGlobal("GlobalConfig", config.GlobalConfig)
|
views.AddGlobal("GlobalConfig", config.GlobalConfig)
|
||||||
views.AddGlobalFunc("GetCountryList", getCountryList)
|
|
||||||
views.AddGlobalFunc("GetLanguageList", getLanguageList)
|
|
||||||
views.AddGlobalFunc("GetTimeZoneList", getTimeZoneList)
|
|
||||||
views.AddGlobalFunc("GetMonthList", getMonthList)
|
views.AddGlobalFunc("GetMonthList", getMonthList)
|
||||||
views.AddGlobalFunc("MakeIntRange", makeIntRange)
|
views.AddGlobalFunc("MakeIntRange", makeIntRange)
|
||||||
views.AddGlobalFunc("MakeYearRange", makeYearRange)
|
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 {
|
views.AddGlobalFunc("CapitalizeString", func(a jet.Arguments) reflect.Value {
|
||||||
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
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
|
// preload the lists in the background
|
||||||
|
|||||||
+5
-12
@@ -81,18 +81,11 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<!-- LEFT SIDEBAR -->
|
<!-- LEFT SIDEBAR -->
|
||||||
<div class="w-48 bg-blue-400 p-2">
|
<div class="w-48 bg-blue-400 p-2">
|
||||||
{{ range amsterdam_leftMenus }}
|
{{ .SetScratch("__menu", AmMenu("top")) }}
|
||||||
<div class="mb-2 mt-2">
|
{{ .SubRender("menu_left.jet") | raw }}
|
||||||
<div class="font-bold mb-1">{{ .Title }}</div>
|
<div class="mb-2 mt-2"> </div>
|
||||||
{{ range .Items }}
|
{{ .SetScratch("__menu", AmMenu("fixed")) }}
|
||||||
{{ if .Link != "" }}
|
{{ .SubRender("menu_left.jet") | raw }}
|
||||||
<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 }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 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 util contains utility definitions.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
|
/* 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 ""
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user